W niniejszym rozdziale:
Zmiany w Servlet API 2.3
Konkluzja
Rozdział 20.
Zmiany w Servlet API 2.3
Krótko przed przekazaniem niniejszej książki do druku firma Sun Microsystems opublikowała Proposed Final Draft (Proponowaną Ostateczną Próbę) specyfikacji Servlet API 2.3. 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 funkcji.
Zmiany w Servlet API 2.3
Servlet API 2.3 właściwie pozostawia nietknięte jądro serwletów, co oznacza, że serwlety osiągnęły wysoki poziom dojrzałości. Większość działań związana była z dodaniem nowych funkcji poza jądrem. Zmiany te to między innymi:
Serwlety wymagają teraz JDK 1.2 lub późniejszego.
Utworzony został (nareszcie) mechanizm filtrów.
Dodane zostały nowe zdarzenia okresu trwałości aplikacji.
Dodana została nowa obsługa internacjonalizacji.
Sformalizowana została technika wyrażania zależności pomiędzy plikami JAR.
Wyjaśnione zostały zasady ładowania klas.
Dodane zostały nowe atrybuty błędów i bezpieczeństwa.
Opuszczona została klasa HttpUtils.
Dodane zostały różne nowe pomocne metody.
Rozszerzone i wyjaśnione zostało kilka zachowań DTD.
Wykonane zostały także inne wyjaśnienia, ale skupiają się one głównie na producentach serwerów, nie ogólnie na programistach serwletów (poza faktem, że programiści ujrzą poprawioną przenośność), tak więc te szczegóły zostaną tu ominięte.
Przed rozpoczęciem przyglądania się zmianom należy zaznaczyć, że wersja 2.3 została udostępniona jedynie jako specyfikacja próbna. Większość opisanych tu funkcji nie będzie działać na wszystkich serwerach. Aby przetestować te funkcje, poleca się pobranie oficjalnego wzorcowego serwera, Apache Tomcat 4.0. Jest to Open Source i może być pobrany za darmo. Tomcat 4.0 jest aktualnie dostępny w wersji beta; obsługa Servlet API 2.3 staje się coraz lepsza, ale ciągle jest niekompletna. Proszę przeczytać plik NEW_SPECS.txt dołączony do Tomcata 4.0 w celu poznania jego poziomu wsparcia dla wszystkich funkcji nowej specyfikacji.
Serwlety w J2SE i J2EE
Jedną z pierwszych rzeczy, jakie zazwyczaj zauważa się na temat Servlet API 2.3 jest fakt, że serwlety zależą teraz od platformy Java 2, Standard Edition (znanej także jako J2SE 1.2 lub JDK 1.2). Ta mała, ale ważna zmiana oznacza, że w serwletach można teraz wykorzystywać funkcje J2SE 1.2 z gwarancją, że serwlety te będą działać na wszystkich kontenerach. Poprzednio funkcje J2SE mogły być wykorzystywane, ale ich obsługa przez serwery nie była obowiązkowa.
Servlet API2.3 ma stać się częścią platformy Java 2, Enterprise Edition 1.3 (J2EE 1.3). Poprzednia wersja, Servlet API 2.2 była częścią J2EE 1.2. Jedyną zauważalną różnicą jest fakt dodania kilku stosunkowo obskurnych znaczników deskryptora związanych z J2EE w deskryptorze web.xml — <resource-env-ref> obsługującego „obiekty administrowane”, takie jak te wymagane przez Java Messaging System (JMS), <res-ref-sharing-scope> pozwalający na współdzielony lub wyłączny dostęp do odwołania do zasobów oraz <run-as> określający identyfikator bezpieczeństwa dla wywołującego EJB. Większość autorów serwletów nie powinna przejmować się tymi znacznikami J2EE; pełny ich opis dostępny jest w specyfikacji J2EE 1.3.
Filtry
Najbardziej znaczącą częścią API 2.3 jest dodanie filtrów. Filtry są obiektami potrafiącymi zmieniać kształt żądania lub modyfikować odpowiedź. Proszę zauważyć, że nie są to serwlety; tak naprawdę nie tworzą one odpowiedzi. Przetwarzają one wstępnie żądanie zanim dotrze ono do serwletu, oraz przetwarzają odpowiedź opuszczającą serwlet. W skrócie, filtry są dojrzałą wersją starego pomysłu „łańcuchów serwletów”. Filtr potrafi:
Przechwycić wywołanie serwletu przed jego wywołaniem.
Sprawdzić żądanie przed wywołaniem serwletu.
Zmodyfikować nagłówki i dane żądania poprzez dostarczenie własnej wersji obiektu żądania, który zostaje dołączony do prawdziwego żądania.
Zmodyfikować nagłówki i dane odpowiedzi poprzez dostarczenie własnej wersji obiektu odpowiedzi, który zostaje dołączony do prawdziwej odpowiedzi.
Przechwycić wywołanie serwletu po jego wywołaniu.
Filtr może być skonfigurowany tak, by działał jako serwlet lub grupa serwletów; ten serwlet lub grupa może zostać przefiltrowana przez dowolną ilość filtrów. Niektóre praktyczne idee filtrów to między innymi filtry uwierzytelniania, filtry logowania i nadzoru, filtry konwersji obrazków, filtry kompresji danych, filtry kodowania, filtry dzielenia na elementy, filtry potrafiące wywołać zdarzenia dostępu do zasobów, filtry XSLT przetwarzające zawartość XML lub filtry łańcuchowe typu MIME (podobne do łańcuchów serwletów).
Filtr jest implementacją javax.servlet.filter i definiuje trzy metody tej klasy:
void setFilterConfig(FilterConfig konfig)
Ta metoda ustawia obiekt konfiguracyjny filtra.
FilterConfig getFilterConfig()
Ta metoda zwraca obiekt konfiguracyjny filtra.
void doFilter(ServletRequest zad, ServletResponse odp, FilterChain lancuch)
Ta metoda wykonuje właściwą pracę filtra.
Serwer wywołuje jeden raz setFilterConfig() w celu przygotowania filtru do działania, następnie wywołuje doFilter() dowolną ilość razy dla różnych obiektów. Interfejs FilterConfig posiada metody pobierające nazwę filtra, jego parametry inicjacji oraz aktywny kontekst filtra. Serwer może również przekazać setFilterConfig() wartość null aby wskazać, że filtr zostaje wyłączony.
UWAGA!!!!
Przewiduje się, że w ostatecznej wersji 2.3 metoda getFilterConfig() zostanie usunięta, a metoda setFilterConfig(FilterConfig konfig)zostanie zastąpiona przez init(FilterConfig) i destroy().
Każdy filtr pobiera aktualne żądanie i odpowiedź w swojej metodzie doFilter(), a także FilterChain zawierający filtry, które muszą stale być przetwarzane. W metodzie doFilter() filtr może wykonać dowolne działanie żądania i odpowiedzi. (Na przykład może zbierać dane przez wywoływanie ich metod, dołączać informacje nadające im nowe zachowanie). Filtr następnie wywołuje lancuch.doFilter() w celu przekazania kontroli następnemu filtrowi. Kiedy wywołanie to wraca, filtr może, na końcu swojej własnej metody doFilter(), wykonać dodatkowe działania na odpowiedzi; na przykład może zapisać informacje na temat odpowiedzi w dzienniku. Jeżeli filtr chce całkowicie zatrzymać przetwarzanie żądania i zyskać pełną kontrolę nad odpowiedzią, może specjalnie nie wywoływać następnego filtra.
Filtr może zmieniać obiekty żądania i odpowiedzi w celu dostarczenia własnego zachowania, zmiany konkretnej metody wywołują implementację wpływającą na późniejsze działania obsługujące żądania. Serwlet API 2.3 dostarcza nowych klas HttpServletRequestWrapper i HttpServletResponseWrapper w tym pomagających. Posiadają one domyślną implementację wszystkich metod żądań i odpowiedzi oraz domyślnie przekazują wywołania do oryginalnych żądań i odpowiedzi. Poniżej przedstawiony jest kod prostego filtra logowania, który zapamiętuje czas trwania wszystkich żądań:
import javax.servlet.*;
import javax.servlet.http.*;
public class FiltrDziennik implements Filter {
FilterConfig konfig;
public void setFilterConfig(FilterConfig konfig) {
this.konfig = konfig;
}
public FilterConfig getFilterConfig() {
return konfig;
}
public void doFilter(ServletRequest zad,
ServletResponse odp,
FilterChain lancuch) {
ServletContext kontekst = getFilterConfig().getServletContext();
long przed = System.currentTimeMillis();
lancuch.doFilter(zad, odp); // nie jest potrzebny żaden parametr łańcucha
long po = System.currentTimeMillis();
kontekst.log("Żądanie" + zad.getRequestURI() + ": " +
(przed-po));
}
}
Kiedy serwer wywołuje setFilterConfig(), filtr zapamiętuje w swojej zmiennej konfig odwołanie do konfiguracji filtra, które później zostanie wykorzystane w metodzie doFilter() w celu pobrania ServletContext. Logika doFilter() jest prosta — obliczenie czasu obsługi żądania i zapamiętanie czasu po zakończeniu przetwarzania. Aby wykorzystać powyższy filtr, trzeba wcześniej zadeklarować go w deskryptorze DTD przy pomocy znacznika <filter>, jak przedstawiono poniżej:
<filter>
<filter-name>
dziennik
</filter-name>
<filter-class>
FiltrDziennik
</filter-class>
</filter>
Powyższy fragment kodu przekazuje serwerowi informację, że filtr o nazwie dziennik jest zaimplementowany w klasie FiltrDziennik. Można przypisać zarejestrowany filtr do konkretnych wzorów URL-i lub nazw serwletów przy pomocy znacznika <filter-mapping>:
<filter-mapping>
<filter-name>log</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Powyższy fragment konfiguruje filtr tak, aby był stosowany do wszystkich żądań serwera (statycznych lub dynamicznych), co jest działaniem odpowiednim dla filtra zapisującego informacje w dzienniku. Jeżeli nastąpi połączenie, dziennik mógłby wyglądać następująco:
Żądanie /indeks.jsp: 10
Zdarzenia okresu trwałości
Drugą bardzo poważną zmianą w Servlet API 2.3 jest dodanie zdarzeń okresu trwałości aplikacji, które umożliwiają powiadomienie obiektów „nasłuchujących", kiedy inicjowane bądź niszczone są konteksty serwletów i sesje, a także kiedy dodawane lub usuwane z kontekstu lub sesji są atrybuty.
Okres trwałości serwletu jest podobny do zdarzeń Swing. Każdy obiekt nasłuchujący zainteresowany obserwacją okresu trwałości ServletContext może zaimplementować interfejs ServletContextListener. Interfejs ten posiada dwie metody:
void contextInitialized(ServletContextEvent z)
Wywoływana z aplikacji WWW, kiedy jest ona po raz pierwszy gotowa o przetwarzania żądań (tzn. podczas uruchomienia serwera lub dodania albo przeładowania kontekstu). Żądania nie są obsługiwane, dopóki metoda nie zwróci swoich wartości.
void contextDestroyed(ServletContextEvent z)
Wywoływana z aplikacji WWW, która powinna zostać zakończona (tzn. podczas wyłączania serwera lub usuwania albo przeładowania kontekstu). Obsługa żądań zostaje zatrzymana przed wywołaniem tej metody.
Klasa ServletContextEvent przekazywana obu metodom zawiera jedynie metodę pobierzZrodloKontekst(), która zwraca kontekst, który jest inicjowany lub niszczony.
Obiekt nasłuchujący zainteresowany obserwacją okresem trwałości atrybutu ServletContext może zaimplementować interfejs ServletContextAttributesListener, który posiada trzy metody:
void attributeAdded(ServletContextAttributeEvent z)
Wywoływana, kiedy atrybut zostaje dodany do kontekstu serwletu.
void attributeRemoved(ServletContextAttributeEvent z)
Wywoływana, kiedy atrybut zostaje usunięty z kontekstu serwletu.
void attributeReplaced(ServletContextAttributeEvent z)
Wywoływana, kiedy atrybut w kontekście serwletu zostaje wymieniony na inny.
Klasa ServletContextAttributeEvent jest rozszerzeniem ServletContextEvent i dodaje metody getName() i getValue(), tak więc obiekt nasłuchujący może uzyskać informacje na temat zmieniających się atrybutów. Jest to przydatna własność, ponieważ aplikacje WWW które muszą zsynchronizować stan aplikacji (atrybuty kontekstu) z czymś w rodzaju bazy danych mogą to teraz uczynić w jednym miejscu.
Model obiektu nasłuchującego sesji jest podobny do modelu obiekt nasłuchującego okresu trwałości. W modelu sesji występuje interfejs HttpSessionListener posiadający dwie metody:
void SessionCreated(HttpSessionEvent z)
Wywoływana podczas tworzenia sesji.
void SessionDestroyed(HttpSessionEvent z)
Wywoływana podczas zniszczenia (unieważnienia) sesji.
Powyższe metody przyjmują egzemplarz HttpSessionEvent z metodą dostępu getSession() w celu zwrócenia sesji, która jest tworzona bądź niszczona. Metody te mogą być zastosowane podczas implementacji interfejsu administratora, który śledzi wszystkich aktywnych użytkowników w aplikacji WWW.
Model sesji posiada również interfejs HttpSessionAttributesListener posiadający trzy metody. Metody te informują obiekt nasłuchujący, kiedy zmieniają się atrybuty i mogłyby być zastosowane, na przykład przez aplikację synchronizującą dane profilu przechowywane w sesji do bazy danych:
void attributeAdded(HttpSessionBindingEvent z)
Wywoływana, kiedy atrybut zostaje dodany do sesji.
void attributeRemoved(HttpSessionBindingEvent z)
Wywoływana, kiedy atrybut zostaje usunięty z sesji.
void attributeReplaced(HttpSessionBindingEvent z)
Wywoływana, kiedy atrybut w sesji zostaje wymieniony na inny.
Jak można się było spodziewać, klasa HttpSessionBindingEvent jest rozszerzeniem HttpSessionEvent i dodaje metody getName() i getValue(). Jedyna dość niezwykła własność jest taka, że klasa zdarzeń nosi nazwę HttpSessionBindingEvent, a nie HttpSessionAttributeEvent. Dzieje się tak z powodu tradycji — API posiadał już klasę HttpSessionBindingEvent, tak więc została ona wykorzystana ponownie. Ten nieco mylący aspekt API może zostać poprawiony w ostatecznej wersji.
Możliwym praktycznym zastosowaniem dla zdarzeń okresu trwałości jest współdzielone połączenie z bazą danych zarządzane przez obiekt nasłuchujący kontekstu. Obiekt ten deklarowany jest w pliku web.xml, w następujący sposób:
<listener>
<listener-class>
com.firma.MojZarzadcaPolaczen
</listener-class>
</listener>
Serwer tworzy egzemplarz klasy nasłuchującej służącej do otrzymywania zdarzeń i wykorzystuje introspekcje w celu określenia, jaki interfejs (lub interfejsy) nasłuchu powinien zostać wykorzystany przez klasę. Należy pamiętać, że ponieważ mechanizm nasłuchujący jest konfigurowany w deskryptorze, można dodawać nowe mechanizmy bez konieczności zmiany kodu. Sam obiekt nasłuchujący mógłby wyglądać następująco:
import javax.servlet.*;
import javax.servlet.http.*;
public class MojZarzadcaPolaczen implements ServletContextListener {
public void contextInitialized(ServletContextEvent z) {
Connection pol = // utworzenie połączenia
z.getServletContext().setAttribute("pol", pol);
}
public void contextDestroyed(ServletContextEvent z) {
Connection pol =
(Connection) z.getServletContext().getAttribute("pol");
try { pol.close(); }
catch (SQLException ignored) { } // zamknięcie połączenia
}
}
Powyższy obiekt nasłuchujący zapewnia dostępność połączenia z bazą danych w każdym nowym kontekście serwletu oraz zamknięcie wszystkich połączeń po zamknięciu kontekstu.
Interfejs HttpSessionActivationListener, kolejny nowy interfejs nasłuchu w API 2.3 jest zaprojektowany do obsługi sesji wędrujących z jednego serwera do drugiego. Obiekt nasłuchujący będący implementacją HttpSessionActivationListener jest powiadamiany o gotowości każdej sesji do wędrówki oraz kiedy sesja gotowa jest do aktywacji na drugim komputerze. Metody te dają aplikacji szansę przesuwania danych niemożliwych do zserializowania pomiędzy wirtualnymi maszynami Javy, lub doklejania i odklejania obiektów zserializowanych pomiędzy pewnego rodzaju modelem obiektów przed lub po migracji. Interfejs ten posiada dwie metody:
void sessionWillPassivate(HttpSessionEvent z)
Sesja ma zamiar dokonać wędrówki. Sesja jest już niedostępna kiedy następuje to wywołanie.
void sessionDidActivate(HttpSessionEvent z)
Sesja została aktywowana. Sesja nie jest jeszcze w użytku, kiedy następuje to wywołanie.
Taki obiekt nasłuchujący jest rejestrowany tak, jak inne. Jednak różnica pomiędzy nimi jest taka, że wywołania migracji i aktywacji najprawdopodobniej będą występować na dwóch różnych serwerach!
Wybranie kodowania znaków
Servlet API 2.3 dostarcza tak bardzo potrzebnej obsługi formularzy wysyłanych w różnych językach. Nowa metoda, zadanie.setCharacterEncoding(String kodowanie) pozwala na poinformowanie serwera o kodowaniu znaków żądania. Kodowanie znaków (albo po prostu kodowanie) to sposób odwzorowania bajtów na znaki. Serwer może wykorzystać określone kodowanie, aby prawidłowo zanalizować parametry i dane POST.
Domyślnie serwer analizuje parametry przy pomocy popularnego kodowania Latin-1 (ISO 8859-1) Niestety jest ono odpowiednie tylko dla języków zachodnioeuropejskich. Kiedy przeglądarka wykorzystuje inne kodowanie, powinna wysyłać informacje o kodowaniu w nagłówku żądania Content-Type, ale prawie żadna przeglądarka nie stosuje się do tej zasady. Metoda setCharacterEncoding() pozwala serwletowi na poinformowanie serwera, które kodowanie jest wykorzystywane (zazwyczaj jest to kodowanie strony, która zawiera formularz); serwer zajmuje się resztą. Na przykład, serwlet pobierający japońskie parametry z formularza zakodowanego przy pomocy Shift_JIS powinien odczytywać parametry w następujący sposób:
// ustawienie kodowania na Shift_JISD
zad.setCharacterEncoding("Shift_JIS");
// Odczytanie parametru przy pomocy tego kodowania
String nazwa = zad.getParameter("nazwa");
Proszę pamiętać o ustawieniu kodowania przed wywołaniem getParameter() lub getReader(). Wywołanie setCharacterEncoding() może zgłosić wyjątek java.io.UnsupportedEncodingException, jeżeli dane kodowanie nie jest obsługiwane. Funkcjonalność ta jest również dostępna użytkownikom API 2.2 i wcześniejszych, jako część klasy com.oreilly.servlet.ParameterParser.
Zależności plików JAR
Plik WAR (plik Web Application Archive — Archiwum Aplikacji WWW, dodane w Servlet API 2.2) często wymaga istnienia i prawidłowego działania na serwerze innych bibliotek JAR. Na przykład, aplikacja WWW wykorzystująca klasę ParameterParser wymaga w ścieżce klas serwera pliku cos.jar. Aplikacja WWW wykorzystująca WebMacro wymaga webmacro.jar.
Przed powstaniem API 2.3, zależności te musiały być udokumentowane (ale czy ktoś tak naprawdę czyta dokumentację!), lub każda aplikacja WWW musiała zawierać wszystkie wymagane pliki JAR w swoim własnym katalogu WEB-INF/lib. Servlet API 2.3 pozwala na zadeklarowanie zależności JAR w WAR przy pomocy pozycji META-INF/MANIFEST.MF archiwum WAR. Jest to standardowy sposób deklarowania zależności plików JAR, ale od API 2.3 pliki WAR muszą oficjalnie obsługiwać ten mechanizm. Jeżeli zależność nie może zostać wypełniona, serwer może delikatnie odrzucić aplikację WWW w trakcie tworzenia zamiast wywoływać obskurne komunikaty o błędach w trakcie uruchomienia. Mechanizm ten pozwala na wysoki stopień ziarnistości. Na przykład można wyrazić zależność w konkretnej wersji opcjonalnego pakietu, a serwer musi ją odnaleźć przy pomocy algorytmu wyszukiwania.
Mechanizmy ładowania klas
Nastąpiła tu mała zmiana, posiadająca jednak ogromne znaczenie — w API 2.3, kontener serwletów (czyli serwer) musi blokować możliwość dostrzeżenia klas implementacji serwera przez klasy aplikacji WWW. Innymi słowy, mechanizmy ładowania klas powinny być od siebie oddzielone.
Nie brzmi to jak wielka zmiana, ale eliminuje możliwość kolizji pomiędzy klasami aplikacji WWW i klasami serwera. Stały się one poważnym problemem z powodu konfliktów analizatora XML. Każdy serwer potrzebuje analizatora XML w celu zinterpretowania plików web.xml, a obecnie wiele aplikacji WWW wykorzystuje analizator XML do obsługi odczytu, manipulacji i zapisu danych XML. Jeżeli analizatory obsługiwały różne wersje DOM lub SAX, powodowało to nienaprawialny konflikt. Oddzielenie zakresów klas elegancko ten problem rozwiązuje.
Nowe atrybuty błędów
Poprzednia wersja interfejsu, Servlet API 2.2 wprowadziła kilka atrybutów żądania, które mogą być wykorzystywane przez serwlety i strony JSP pełniące funkcję celu zasady <error-page>. Wcześniej w niniejszej książce opisano, że zasady <error-page> pozwalają na skonfigurowanie aplikacji WWW tak, aby konkretne kody stanu lub typy wyjątków powodowały wyświetlenie konkretnych stron:
<web-app>
<!-- ..... -->
<error-page>
<error-code>
404
</error-code>
<location>
/404.html
</location>
</error-page>
<error-page>
<exception-type>
javax.servlet.ServletException
</exception-type>
<location>
/servlet/WyswietlBlad
</location>
</error-page>
<!-- ..... -->
</web-app>
Serwlet umieszczony w <location> zasady <error-page> mógł otrzymywać następujące trzy atrybuty:
javax.servlet.error.status_code
Liczba Integer przekazująca kod stanu błędu, jeżeli taki istnieje.
javax.servlet.error.exception_type
Egzemplarz Class wskazujący na typ wyjątku, który spowodował błąd, jeżeli taki istnieje.
javax.servlet.error.message
Łańcuch String przekazujący wiadomość o błędzie, przekazywany do konstruktora wyjątku.
Przy pomocy powyższych atrybutów serwlet mógł wygenerować stronę błędu dostosowaną do błędu, jak przedstawiono poniżej:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class WyswietlBlad extends HttpServlet {
public void doGet(HttpServletRequest zad, HttpServletResponse odp)
throws ServletException, IOException {
odp.setContentType("text/html");
PrintWriter wyj = odp.getWriter();
String kod = null, wiadomosc = null, typ = null, uri = null;
Object kodObi, wiadomoscObi, typObi;
// Odczytanie trzech możliwych atrybutów błędów. Niektóre mogą mieć wartość null
kodObi = zad.getAttribute("javax.servlet.error.status_code");
wiadomoscObi = req.getAttribute("javax.servlet.error.message");
typObi = req.getAttribute("javax.servlet.error.exception_type");
// Konwersja atrybutów na wartości łańcuchowe
// Dlatego, że niektóre stare typy serwerów zwracają typy String
// a nowe typy Integer, String i Class
if (kodObi != null) kod = kodObi.toString();
if (wiadomoscObi != null) wiadomosc = wiadomoscObi.toString();
if (typObi != null) typ = typObi.toString();
// Powód błędu to kod stanu lub typ wyjątku
String powod = (kod != null ? kod : typ);
wyj.println("<HTML>");
wyj.println("<HEAD><TITLE>" + powod + ": " + wiadomosc +
"</TITLE></HEAD>");
wyj.println("<BODY>");
wyj.println("<H1>" + powod + "</H1>");
wyj.println("<H2>" + wiadomosc + "</H2>");
wyj.println("<PRE>");
}
wyj.println("</PRE>");
wyj.println("<HR>");
wyj.println("<I>Błąd przy dostępie do " + zad.getRequestURI() + "</I>");
wyj.println("</BODY></HTML>");
}
}
Ale co by się stało, gdyby strona błędu mogła zawierać ścieżkę stosu wyjątku lub URI serwletu, który naprawdę spowodował problem, (ponieważ nie zawsze jest to początkowo zażądany URI)? W API 2.2 nie było to możliwe. W API 2.3 informacje te są dostępne poprzez dwa nowe atrybuty:
javax.servlet.error.exception
Obiekt Throwable, zawierający właściwy zgłoszony wyjątek.
javax.servlet.error.request_uri
Łańcuch String przekazujący URI zasobu sprawiającego problem.
Atrybuty te pozwalają na dołączenie do strony błędu ścieżki stosu wyjątku oraz URI zasobu sprawiającego problem. Serwlet przedstawiony poniżej został ponownie napisany tak, aby wykorzystywał nowe atrybuty. Proszę zauważyć, że elegancko przerywa działanie, jeżeli one nie istnieją. Dzieje się tak w celu zachowania wstecznej kompatybilności.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class WyswietlBlad extends HttpServlet {
public void doGet(HttpServletRequest zad, HttpServletResponse odp)
throws ServletException, IOException {
odp.setContentType("text/html");
PrintWriter wyj = odp.getWriter();
String kod = null, wiadomosc = null, typ = null, uri = null;
Object kodObi, wiadomoscObi, typObi;
Throwable throwable;
// Odczytanie trzech możliwych atrybutów błędów. Niektóre mogą mieć wartość null
kodObi = zad.getAttribute("javax.servlet.error.status_code");
wiadomoscObi = req.getAttribute("javax.servlet.error.message");
typObi = req.getAttribute("javax.servlet.error.exception_type");
throwable = (Throwable)
zad.getAttribute("javax.servlet.error.exception");
uri = (String)
zad.getAttribute("javax.servlet.error.request_uri");
if (uri == null) {
uri = zad.getRequestURI(); // gdyby nie podano URI
}
// Konwersja atrybutów na wartości łańcuchowe
if (kodObi != null) kod = kodObi.toString();
if (wiadomoscObi != null) wiadomosc = wiadomoscObi.toString();
if (typObi != null) typ = typObi.toString();
// Powód błędu to kod stanu lub typ wyjątku
String powod = (kod != null ? kod : typ);
wyj.println("<HTML>");
wyj.println("<HEAD><TITLE>" + powod + ": " + wiadomosc +
"</TITLE></HEAD>");
wyj.println("<BODY>");
wyj.println("<H1>" + powod + "</H1>");
wyj.println("<H2>" + wiadomosc + "</H2>");
wyj.println("<PRE>");
if (throwable != null) {
throwable.printStackTrace(wyj);
}
wyj.println("</PRE>");
wyj.println("<HR>");
wyj.println("<I>Błąd przy dostępie do " + uri + "</I>");
wyj.println("</BODY></HTML>");
}
}
Nowe atrybuty bezpieczeństwa
Servlet API 2.3 dodaje również dwa nowe atrybuty żądania, które mogą pomóc serwletowi w podejmowaniu dobrze umotywowanych decyzji dotyczących obsługi bezpiecznych połączeń HTTPS. Do żądań wykonanych przy pomocy HTTPS serwer dołączy następujące nowe atrybuty żądania:
javax.servlet.request.cipher_suite
Łańcuch String reprezentujący typ szyfrowania stosowany przez HTTPS, jeżeli taki występuje.
javax.servlet.request.key_size
Liczba Integer reprezentująca wielkość algorytmu w bitach, jeżeli taka występuje.
Serwlet może wykorzystać powyższe atrybuty do programowej decyzji, czy połączenie jest na tyle bezpieczne, że można z niego skorzystać. Aplikacja może odrzucić połączenia o niewielkiej liczbie bitów, lub algorytmy niegodne zaufania. Na przykład, serwlet mógłby wykorzystać poniższą metodę w celu upewnienia się, że jego połączenie wykorzystuje klucz przynajmniej 128-bitowy:
public boolean czyPonad128(HttpServletRequest zad) {
Integer wielkosc = (Integer) zad.getAttribute("javax.servlet.request.key_size");
if (wielkosc == null || wielkosc.intValue() < 128) {
return false;
}
else {
return true;
}
}
UWAGA!!!!
Nazwy atrybutów w wersji próbnej wykorzystują myślniki zamiast kresek dolnych. Jednak zostaną one zmienione w wersji ostatecznej, jak przedstawiono powyżej, w celu uzyskania większej spójności z nazwami istniejących atrybutów.
Niewielkie poprawki
W Servlet API 2.3 znajdzie się także pewna ilość drobnych zmian. Po pierwsze, metoda getAuthType(), która zwraca typu uwierzytelnienia wykorzystywanego do identyfikacji klienta została zdefiniowana tak, aby zwracać jedną z czterech nowych stałych typu String klasy HttpServletRequest — BASIC_AUTH, DIGEST_AUTH, CLIENT_AUTH i FORM_AUTH. Umożliwia to wykorzystanie uproszczonego kodu:
if (zad.getAuthType() == zad.BASIC_AUTH) {
//obsługa uwierzytelniania podstawowego
}
Oczywiście cztery stałe posiadają ciągle tradycyjne wartości String, tak więc poniższy kod z API 2.2 również działa, ale nie jest ani tak szybki, ani elegancki. Proszę zwrócić uwagę na odwrócone sprawdzenia equals() w celu uniknięcia wyjątku NullPointerException, jeżeli getAuthType() zwróci null:
if ("BASIC".equals(zad.getAuthType())) {
//obsługa uwierzytelniania podstawowego
}
Inną zmianą w API 2.3 jest opuszczenie HttpUtils. Klasa HttpUtils była zawsze uważana za zbiór różnych statycznych metod — wywołań, które mogły być czasami użyteczne, ale równie dobrze mogły się znaleźć w innych miejscach. Klasa ta zawierała metody służące do zrekonstruowania oryginalnego URL-a z obiektu żądania i do przeformatowania danych parametrów do tablicy asocjacyjnej. API 2.3 przesuwa tę funkcjonalność do obiektu żądania, co jest miejscem bardziej odpowiednim i opuszcza HttpUtils. Nowe metody obiektu żądania wyglądają następująco:
StringBuffer zad.getRequestURL()
Zwraca StringBuffer zawierający URL oryginalnego żądania, odtworzony na podstawie informacji żądania.
java.util.Map zad.getParameterMap()
Zwraca niemodyfikowalne odwzorowanie Map parametrów żądania. Nazwy parametrów pełnią funkcję kluczy, a wartości parametrów funkcję wartości odwzorowania. Nie zapadła decyzja co do obsługi parametrów o wielu wartościach; najprawdopodobniej wszystkie wartości zostaną zwrócone jako String[]. Metody te wykorzystują nową metodę setCharacterEncoding() do obsługi konwersji znaków.
API 2.3 dodaje również dwie nowe metody do ServletContext, które pozwalają na odczytanie nazwy kontekstu i wyświetlenia wszystkich przechowywanych przez niego zasobów:
String kontekst.getServletContextName()
Zwraca nazwę kontekstu zadeklarowaną w pliku web.xml.
java.util.Set kontekst.getResourcePaths()
Zwraca ścieżki do wszystkich zasobów dostępnych w kontekście, jako niemodyfikowalny zbiór obiektów String. Każdy String posiada wiodący ukośnik (/) i powinien być uważany za względny do katalogu macierzystego kontekstu.
Pojawiła się także nowa metoda obiektu odpowiedzi zwiększająca kontrolę programisty nad buforem odpowiedzi. API 2.2 wprowadził metodę odp.reset() pozwalającą na usunięcie odpowiedzi i wyczyszczenie jej głównej części, nagłówków i kodu stanu. API 2.3 dodaje odp.resetBuffer(), który czyści jedynie główną część odpowiedzi:
void resetBuffer()
Czyści bufor odpowiedzi, ale pozostawia nagłówki i kod stanu. Jeżeli odpowiedź został już wysłana, zgłasza wyjątek IllegalStateException.
I wreszcie, po długich naradach grupy ekspertów, Servlet API 2.3 wyjaśnia raz na zawsze, co dokładnie dzieje się podczas wywołania odp.sendRedirect("indeks.html") w przypadku serwletu nie działającego w kontekście macierzystym serwera. Niejasność wynikała z tego, że Servlet API 2.2 wymaga, aby niekompletna ścieżka taka jak "/indeks.html" została przetłumaczona przez kontener serwletów na ścieżkę kompletną, nie określa jednak zasad obsługi ścieżek kontekstów. Jeżeli serwlet wykonujący wywołanie znajduje się w kontekście o ścieżce "/sciezkakontekstu", czy URI przekierowania powinien być określony względem katalogu macierzystego kontenera (http://serwer:port/indeks.html), czy katalogu macierzystego kontekstu (http://serwer:port/sciezkakontekstu/indeks.html)? W celu zapewnienia maksymalnej przenośności zdefiniowanie zachowania jest konieczne. Po długiej debacie eksperci wybrali tłumaczenie ścieżek względem katalogu macierzystego serwera. Osoby pragnące tłumaczenia względem kontekstu powinny dodać na początku URI wynik wywołania getContextPath().
Wyjaśnienia deskryptora DTD
Servlet API 2.3 rozwiewa kilka niejasności związanych z zachowaniem deskryptora web.xml. Obowiązkowe stało się przycięcie wartości tekstowych w pliku web.xml przed jego zastosowaniem. (W standardowym nie sprawdzanym XML wszystkie miejsce są ogólnie rzecz biorąc zachowywane.) Zasada ta zapewnia, że się poniższe pozycje będą traktowane identycznie:
<servlet-name>witaj --> </servlet-name[Author:F&L] >
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.
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.
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).
2 Część I ♦ Podstawy obsługi systemu WhizBang (Nagłówek strony)
2 C:\0-praca\Java Servlet - programowanie. Wyd. 2\r20-t.doc
Błąd w ksiąążce