r05-05, Programowanie, ! Java, Java Server Programming


W tym rozdziale:

Rozdział 5.

Przesyłanie informacji 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.

Przesyłanie standardowej odpowiedzi

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

Przykład 5.1.

Jeszcze raz „Hello”

import java.io.* ;

import javax.servlet.* ;

import javax.servlet.http. *;

public class HelloWorld extends HttpServlet {

public void doGet(HttpServletRequest req, HttpServletResponse res)

throws ServletException, IOException {

res. setContentType (" tekst/html") ;

PrintWriter out = res.getWriter();

out.println("<HTML>") ;

out.println("<HEAD><TITLE>Hello World</TITLE></HEAD>") ;

out.println("<BODY>") ;

out.println("<BIG>Hello World</BIG>") ;

out.println("</BODY></HTML>") ;

}

}

Aplet ten używa dwóch metod oraz jednej klasy, które do tej pory zostały omówione 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)

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

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

public PrintWriter ServletResponse.getWriter() throws IOException

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

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

public ServletOutputStream ServletResponse.getOutputStream () throws IOException

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[].

Korzystanie ze połączeń stałych

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

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():

public void ServletResponse.setContentLength(int len)

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.*;

public class KeepAlive extends HttpServlet {

public void doGet(HttpServletRequest req, HttpServletResponse res)

throws ServletException, IOException {

res.setBufferSize(8*1024); // 8K buffer

// Poproś o bufor 16k bitów; nie ustalaj długości treści

res.setBufferSize(16 * 1024);

PrintWriter out = res. getWriter () ;

out.println("<HTML>") ;

out.println("<HEAD><TITLE>Hello World</TITLEx/HEAD>) ;

out.println("<BODY>") ;

out.println("<BIG>Mniej niż 16 k korpusu odpowiedzi</BIG>") ;

out.println("</BODY></HTML>°) ;

Aplet wywołuje metodę setBufferSize() aby poprosić o minimum 16kB bufor odpowiedzi, a nastę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.

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

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.

Regulowanie bufora odpowiedzi

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

Public void ServletResponse.setBufforSize(int size)

throws IllegalStateException

Serwer może zapewnić bufor większy niż żądany — może zdecydować, że bufory będą utrzymywane w np. 8k blokach, w celu umożliwienia ponownego użytku. Większy 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():

public boolean ServletResponse.isCommitted()

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

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

public void ServletResponse.reset() throws IllegalStateException

Metoda ta musi zostać wywołana zanim jeszcze odpowiedź nie zostanie zatwierdzona, w przeciwnym 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:

public void ServletResponse.flushBuffer() throws IOException

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.*;

public class Buffering extends HttpServlet {

public void doGet(HttpServletRequest req, HttpServletResponse res)

throws ServletException, IOException {

res.setBufferSize(8 * 1024); // 8-kilowy bufor

res. setContentType (" tekst/html") ;

PrintWriter out = res.getWriter();

int size = res.getBufferSize(); // odsyła 8096 lub większy

// Zapisz rozmiar domyślny, w rejestrze zdarzeń

log("Domyślny rozmiar bufora to " + rozmiar);

out.println("Klient nie będzie tego widział");

res.reset();

out.println("Tego też nie będzie widział klient!");

res.reset() ;

out.println("A tego nie będzie można zobaczyć jeżeli zostanie

wywołana metoda sendError()");

if (req.getParameter("ważny_parametr") == null) {

res. sendError (res. SC_BAD_REQUEST, " ważny_parametr potrzebny") ;

}

}

}

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 mnemonicznego

Kod

Komunikat domyślny

Znaczenie

S.C._OK.

200

OK

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

S.C._NO_CONTENT

204

Nie ma treści

Zlecenie zakończyło się sukcesem, lecz nie było żadnego korpusu 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_PERMANENTLY

301

Stale przenoszone

Zamawiany zasób ma stale przenoszone miejsce lokalizacji. Przyszłe 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_TEMPORARILY

302

Czasowo przeniesione

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

S.C._UNAUTHORIZED

401

Nieupoważniono

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

S.C._NOT_FOUND

404

Nie znaleziono

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

S.C._INTERNAL_SERVER_ERROR

500

Błąd wewnętrzny serwera

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

S.C._NOT_IMPLEMENTED

501

Nie wprowadzono

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

S.C._SERVICE_UNAVAILABLE

503

Obsługa niedostępna

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

Ustanawianie kodu statusu

Aby kod statusu odpowiedzi aplet może skorzystać z metody setStatus():

public void HttpServletResponse. setStatus (int sc)

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

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():

public void HttpServletResponse.sendError(int sc)

throws IQException, IllegalStateException

public void HttpServletResponse.sendError(intsc, String sm)

throws IOException, IllegalStateException

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

Ulepszanie apletu „ViewFile” przy pomocy kodów statusu

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

// Odeślij plik

try {

ServletUtils.returnFile(file, out);

}

catch (FileNotFoundException e) {

out.println("Plik nie odnaleziony");

}

Bez ustalenia kodu statusu, jedyną rzeczą, którą serwer może zrobić jest napisanie wyjaśnienia problemu, przesyłając to wyjaśnienie jako, na ironię, część strony, która miała zawierać treści pliku. Jednakże stosując 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.

0x01 graphic

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-Authenticate

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

Content-Encoding

Określa schemat użyty do kodowania korpusu odpowiedzi. Przykładowe 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

Ustawianie nagłówków HTTP

Klasa HttpServletResponse daje dostęp do wielu metod, pomagających apletom w ustalaniu nagłówków HTTP odpowiedzi. Aby ustalić wartość nagłówka używamy metody setHeader():

public void HttpServletResponse.setHeader(String name, String value)

Metoda ta ustala wartość nagłówka nazwanego, jako String. Nazwa nie uwzględnia wielkości liter, tak jak 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():

public void HttpServletResponse. setDateHeader (String name, long date)

Metoda ta ustala wartość nagłówka nazwanego na określoną datę i czas. Metoda akceptuje wartość daty 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:

public void HttpServletResponse.setIntHeader(String name, int value)

Metoda ta ustala wartość nagłówka nazwanego na int. Jeżeli nagłówek został już uprzednio ustalony stara wartość jest zastępowana przez nową.

Metoda containsHeader() demonstruje sposób, w jaki można sprawdzić czy nagłówek już istnieje:

public boolean HttpServletResponse.containsHeader(String name)

Metoda ta odsyła true w przypadku gdy nagłówek nazwany został już ustalony, w przeciwnym razie metoda 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.

public void HttpServletResponse.addHeader(String name, String value)

public void HttpServletResponse.addDateHeader (String name, long value)

public void HttpServletResponse.addIntHeader(String name, int value)

Metody te ustalają nagłówki na daną wartość, lecz podczas gdy tradycyjna metoda setHeader() zamieniłaby jakąkolwiek istniejącą wartość lub wartości, metoda addHeader() pozostawia dotychczasowe ustawienia bez zmian, ustalając tylko wartość dodatkową.

I w końcu, w specyfikacji HTML 3.2 został przedstawiony alternatywny sposób ustalania wartości nagłówków, zakładający użycie znacznika <META HTTP_EQUIV> wewnątrz samej strony HTML:

<META HTTP-EQUIV="nazwa" CONTENT=" wartość ">

Znacznik ten musi zostać przesłany jako część sekcji <HEAD> strony HTML. Powyższa technika nie 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.*;

public class SiteSelector extends HttpServlet {

Vector sites = new Vector();

Random random=new Random();

public void init() throws ServletException {

sites.addElement("http://www.oreilly.com/catalog/jservlet2");

sites.addElement("http://www.servlets.com");

sites.addElement("http://Java.sun.com/products/servlet");

sites.addElement("http://www.newlnstance.com");

}

public void doGet (HttpServletRequest req, HttpServletResponse res)

throws ServletException, IOException {

res.setContentType("tekst/html");

PrintWriter out = res.getWriter() ;

int sitelndex = Math. abs( random, nextInt ()) % sites. size();

String site = (String)sites.elementAt(sitelndex);

res.setStatus(res.SC_MOVED_TEMPORARILY) ;

res.setHeader("Lokalizacja",site);

}

}

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():

public void HttpServletResponse.sendRedirect(String location)

throws IOException, IllegalStateException

Dla potrzeb naszego przykładu powyższe da wiersze upraszczane są do:

res.sendRedirect(site);

Metoda ta przekierowuje odpowiedź do określonej lokalizacji, automatycznie ustalając kod statusu 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.

Nadzorowanie łączników do innych stron

Przekierowywanie może być również wykorzystane do zdobycia informacji na temat tego z jaką lokalizacją łączą się klienci po opuszczeniu naszej strony. Załóżmy, że mamy kilka stron zawierających listy łączników do innych stron. zamiast łączenia bezpośrednio z zewnętrznymi stronami, możemy połączyć się z apletem przekierowywującym, który może zarejestrować każdorazowy wybór zewnętrznego łącznika. HTML wygląda wtedy w następujący sposób:

<a href="/goto/http://www.servlets.com">Servlets.com</a>

Aplet może być zarejestrowany ażeby obsługiwać przedrostek ścieżki /goto/*, gdzie uzyska wybrany URL 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.*;

public class GoTo extends HttpServlet {

public void doGet(HttpServletRequest req, HttpServletResponse res)

throws ServletException, IOException {

// Określ stronę do której chcą się udać

String site = req.getPathInfo();

String query = req.getQueryString();

// Obsłuż błędne zlecenie

if (site == null) {

res.sendError(res.SC_BAD_REQUEST, "Wymagane informacje

dodatkowej ścieżki");

// Odetnij interlinię "/" oraz dodaj ciąg zapytań

// Zakładamy, że ścieżka informacji URL jest zawsze bezwzględna

String url=site.substring(1)+(query==null ?"" : "?" +

query");

// Zaloguj URl będący przedmiotem zlecenia i przekieruj

log (url); //lub pisz do pliku specjalnego

res.sendRedirect(url);

}

}

Aplet podobny do omawianego powyżej jest w stanie obsługiwać aliasing, tak że /got/servlets automatycznie przekierowywałoby do http://www.servlets.com. Informacje dotyczące sprzedaży i dystrybucji podobnych zamienników można znaleźć na stronie http://go.to oraz na http://i.am.

Klienckie ściąganie z serwera

Klienckie ściąganie z serwra przypomina przekierowywanie, istnieje jednak, jedna istotna różnica: przeglądarka wyświetla w danym momencie treść z pierwszej strony, a następnie czeka pewien czas na pobranie i wyświetlenie treści następnej strony. Nazwa brzmi klienckie ściąganie z serwera ponieważ to klient jest odpowiedzialny za ściąganie treści następnej strony.

Ktoś mógłby zapytać: „czy jest to metoda skuteczna?” Odpowiedź brzmi: „tak”. Po pierwsze, treść 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:

setHeader("Odśwież", "3; URL=http://home.netscape.com");

Przykład 5.6 ukazuje aplet, który wykorzystuje klienckie ściąganie z serwera w celu wyświetlenia 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.*;

public class ClientPull extends HttpServlet {

public void doGet(HttpServletRequest req, HttpServletResponse res)

throws ServletException, IOException {

res. setContentType (" tekst/zwykły") ;

PrintWriter out = res.getWriter() ;

res.setHeader("Odśwież", "10");

out.println(new Date().toString()) ;

}

}

Jest to przykład animacji opartej na tekście — animacje graficzne omówimy w następnym rozdziale. Zwróć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.* ;

public class ClientPullMove extends HttpServlet {

static final String NEW_HOST = "http://www.oreilly.com";

public void doGet(HttpServletRequest req, HttpServletResponse res)

throws ServletException, IOException {

res.setContentType("tekst/html") ;

PrintWriter out = res.getWriter();

String newLocation = NEW_HOST + req.getRequestURI();

res.setHeader("Odśwież", "10; URL=" + newLocation) ;

out.println("URL będący przedmiotem zleceni został przekazany do

innego komputera centralnego.<BR>");

out.println("Jego nowa lokalizacja to " + newLocation + "<BR>");

out.println(" Twoja przeglądarka połączy Cię z tym miejscem za 10

sekund.");

}

}

Aplet ten generuje nową lokalizację z URI będącego przedmiotem zlecenia, które pozwala na przekierowywanie wszystkich zleceń złożonych na stary serwer, na tą samą ścieżkę na nowym serwerze. Przy użyciu deskryptora wdrożenia web.xml, aplet ten mógłby być skonfigurowany do obsługi każdego zlecenia, stopniowo przekazując klientów do nowej lokalizacji.

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:

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.

Należy pamiętać, że wiarygodne aplety wywołujące poza „piaskownicą” mechanizmu zarządzania bezpieczeństwem mają możliwości, które mogą potencjalnie wywołać szkody na serwerze. Dla przykładu aplet może zamazać obszar pliku serwera lub nawet wywołać metodę System.exit(). Jest również prawdą, że aplet nie powinien nigdy spowodować szkód (chyba, że przypadkowo, co jest raczej trudne w przypadku wywoływania metody System.exit()). Jednak, jeżeli jest taka potrzeba, nawet aplety wiarygodne mogą być (i często są) uruchamiane wewnątrz całkiem „łagodnego” lecz sprawdzającego poprawność programu zarządzania bezpieczeństwem.

Właściwe opisanie problemu klientowi, nie może być wyłącznie pozostawione technologii języka Java. Istnieje wiele spraw, które trzeba wsiąść pod uwagę, takich jak np.:

Jak wiele powiedzieć klientowi?

Czy aplet powinien przesłać standardowy kod statusu strony błędu, proste wyjaśnienie błędu czy też (w przypadku zgłoszonego wyjątku) szczegółowy ślad kaskady? A co kiedy aplet ma odesłać nie-tekstową treść, taką jak np. obrazek?

Jak zarejestrować problem?

Czy powinien on zostać zapisany w pliku? czy do rejestru zdarzeń serwera, ma być przesłany do klienta czy też zignorowany?

Jak pozbyć się problemu?

Czy ta sama kopia apletu może obsługiwać następujące po sobie zlecenia? Lub czy serwer jest uszkodzony i trzeba go powtórnie załadować?

Odpowiedzi na powyższe pytania zależą od apletu oraz zastosowania dla którego został on zaprojektowany, powinny one być kierowane do każdego apletu na zasadzie „przypadek po przypadku”. Sposób w jaki poradzimy sobie z błędami zależy od nas samych i powinien być oparty na poziomie niezawodności i odporności na błędy, wymaganymi dla naszego apletu. Kolejną kwestią, której poświęcimy naszą uwagę jest ogólne omówienie mechanizmów obsługi błędu apletu, których używamy w celu wdrożenia jakiejkolwiek ze strategii.

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.

Konfiguracja stron błędu

Może się zdarzyć, ze zamiast używania standardowych stron błędu serwera, będziemy chcieli utworzyć zestaw standardowych stron błędu dla aplikacji WWW, stron które będą mogły być wykorzystywane bez względu na to gdzie aplikacja zostanie zainstalowana. Dla przykładu aplikacja WWW może zostać skonfigurowana za pomocą strony błędu 404, zawierającej pole wpisywania wyszukiwarki, w przypadku gdy użytkownik zechciałby skorzystać z pomocy w lokalizacji zasobu NotFound. Możemy tego dokonać poprzez bezpośrednie użycie metody setStatus() i spowodowanie, że każdy aplet będzie generował identyczną stronę błędu, jednakże lepszym podejściem jest zainstalowanie reguły <error-page> w deskryptorze wdrożenia web.xml. Na przykładzie 5.8 została zaprezentowana omawiana sytuacja.

Przykład 5.8.

Konfiguracja stron błędu 400 i 404

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

<!DOCTYPE web-app

PUBLIC "-//Sun Microsystems, Inc.//Aplikacja WWW DTD 2.2//EM"

"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">

<web-app>

<!--.....-->

<error-page>

<error-code>

400

</error-code>

<location>

/400.html

</location>

</error-page>

<error-page>

<error-code>

404

</error-code>

<location>

/404.html

</location>

</error-page>

</web-app>

Powyższe dwa pola wpisywania <error-page> „mówią” serwerowi, że jakiekolwiek wywołanie do 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:

<META HTTP-EQUIV="Odśwież" TREŚĆ"=0;

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.*;

public class ErrorDisplay extends HttpServlet{

public void doGet(HttpServletRequest req, HttpServletResponse res)

throws ServletException, IOException {

res.setContentType("tekst /html") ;

PrintWriter out = res.getWriter();

Object codedbj =

req.getAttribute("javax.servlet.error.status_code");

Object messageObj = req. getAttribute

("javax.servlet.error.message");

// Kod i komunikat nigdy nie powinny być równe zero w przypadku

// apletów zgodnych API 2.2

String code = (codeObj != null ?

codeObj.toString() : "Brakujący kod statusu");

String message = (messageObj != null ?

messageObj . toString () : "Komunikat Brakującego Błędu") ;

out.println("<HTML>");

out.println("<HEAD><TITLE>" + kod + ": " + komunikat +

"</TITLE><HEAD>");

out.println("<BODY>");

out.println("<H1>" + kod + "</H1>");

out.println("<H2>" + komunikat + "</H2>");

out.println("<HR>");

out.println("<I>dostęo do błędu " + req.getRequestURI() + "</I>");

out.println (" < /BODY></HTML>") ;

}

}

Ścieżka do tego apletu może zostać skonfigurowana jako przeznaczenie dl wszystkich kodów statusu, któ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():

public void GenericServlet. log (String msg)

public void GenericServlet.log(String msg, Throwable t)

Metoda jedno-argumentowa zapisuje dany komunikat w dzienniku zdarzeń apletu, którym 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:

public static String getStackTraceAsString(Throwable t) {

ByteArrayOutputStream bytes = new ByteArrayOutputStream();

PrintWriter writer = new PrintWriter(bytes, true);

t.printStackTrace(writer) ;

return bytes.toString() ;

}

Oto sposób w jaki aplet ViewFile jest w stanie dostarczyć informacji, zawierających ślad 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)) ;

}

Wydruk wyjściowy dla przykładowego wyjątku został przedstawiony na rysunku 5.2.

0x01 graphic

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.

Wyjątki apletowe „ServletException”

ServletException jest podklasą klasy java.lang.Exception, która jest określona dla apletów — klasa ta jest zdefiniowana w zestawie javax.servlet. Aplet zgłasza ten wyjątek, aby zasygnalizować swój ogólny problem. Ma on takich samych konstruktorów co java.lang.Exception: jednego, który nie przyjmuje żadnych argumentów i jednego, który przyjmuje pojedynczy ciąg komunikatu:

javax.servlet.ServletException()

javax.servlet.ServletException(String msg)

Klasa ServletException może również obsługiwać „główną przyczynę” obiektu Throwable. Pozwala 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():

public Throwable ServletException.getRootCause()

W razie gdy nie istnieje żaden wyjątek zagnieżdżony, wywołanie odsyła null.

Wyjątki apletowe „UnavailableException”

Zestaw javax.servlet określa jedną podklasę klasy ServletExceptionUnavailableException, możemy oczywiście dodać swoją własną. Wyjątek ten informuje, że aplet jest niedostępny — tymczasowo lub trwale.

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łą.

Wyjątek UnavailableException ma dwóch konstruktorów:

javax.servlet.UnavaliableException(string msg)

javax.servlet.UnavaliableException(String msg, int seconds)

Konstruktor jedno-argumentowy tworzy nowy wyjątek, informujący o tym, że aplet jest trwale niedostępny, 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.

Konfiguracja Stron Wyjątków

Generalnie, zachowanie serwera podczas wyłapywania przez niego wyjątków apletu zależy od samego serwera, jednakże aplikacja WWW może wskazać poprzez swój deskryptor wdrożenia, zestaw stron błędu dla obsługi określonych rodzajów wyjątków. Strony błędu określane są przy użyciu znacznika <error-page> (tak jak w przypadku kodów statusu), z wyjątkiem sytuacji kiedy zastępujemy znacznik <error-code> znacznikiem <exception-type> (porównaj przykład 5.10).

Przykład 5.10.

Konfiguracja stron błędu wyjątków

<?xml versions"1.0"kodowanie="ISO-8859-l"?>

<!DOCTYPE web-app

PUBLIC "-//Sun Microsystems, Inc.//Aplikacja WWW DTD 2.2//EN"

"http://Java.sun.com/j2ee/dtds/web-app_2.2.dtd">

<web-app>

<!-- ..... -->

<error-page>

<exception-type>

javax.servlet.ServletException

</exception-type>

<location>

/servlet/ErrorDisplay

</location>

</error-page>

</web-app>

Pozycja wskazuje, że jakikolwiek ServletException zgłoszony do serwera, powinien zostać wyświetlony przy użyciu apletu ErrorDisplay. Zwróćmy uwagę, iż znacznik <exception-type> musi być w pełni zgodny z nazwą zestawu; sam ServletException nie będzie działał. Zasada ta stosuje się również do wszystkich podklas klasy servletException, takich jak UnavailableException (jeżeli nie ma w deskryptorze wdrożenia bardziej uściślającej zasady, którą stosuje się najpierw.

Dla przeznaczeń <location> (lokalizacji) dynamicznych, aplet udostępnia dwa atrybuty zleceniowe, służące 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.*;

public class ErrorDisplay extends HttpServlet {

public void doGet (HttpServletRequest req, HttpServletResponse res)

throws ServletException, IOException {

res.setContentType("tekst/html") ;

PrintWriter out = res.getWriter();

String code = null, message = null, type = null;

Object codeObj, messageObj, typeObj;

// Pobierz trzy możliwe atrybuty błędu, niektóre z nich mogą być

równe // zero

codeObj = req.getAttribute("j avax.servlet.error.status_code") ;

messageObj = req.getAttribute("javax.servlet.error.message") ;

typeObj = req.getAttribute("javax.servlet.error.exception_type") ;

// Zamień atrybuty na wartości ciągu

if (codeObj != null) code = codeObj.toString();

if (messageObj != null) message = messageObj.toString();

if (typeObj != null) type = typeObj.toString();

// Przyczyną błędu jest albo kod statusu albo typ błędu

String reason = (code != null ? code : type);

out .println ("<HTML>") ;

out.println("<HEAD><TITLE>" + przyczyna + ": " + komunikat +

"</TITLE></HEAD>") ;

out.println("<BODY>");

out.println("<H1>" + przyczyna + "</H1>");

out.println("<H2>" + komunikat + "</H2>");

out.println("<HR>") ;

out.println("<I>Łaczenie z błędem " + req.getRequestURI() +

"</I>");

out.println( "</BODY></HTML>") ;

}

}

Niestety, tylko typ wyjątku i komunikat — a nie ślad kaskady wyjątku — są dostępne dla apletu ErrorDisplay, ograniczając użyteczność strony błędu wyjątku. Aby zagwarantować wyświetlanie lub rejestrację śladu 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.

Jak rozpoznać, że nikt nie oczekuje na sygnały?

Czasem bywa i tak, że klienci przerywają połączenie z serwerami. Tak, to prawda to jest nie fair, lecz niestety zdarza się. Raz spowodowane jest to tym, że klient połączył się ze złą stroną, innym znów razem powodem jest zbyt długi czas generowania odpowiedzi apletu. Pamiętajmy, że przez cały czas kiedy aplet przygotowuje swoją odpowiedź, użytkownik może przerwać połączenie — klikając po prostu przycisk „stop”. Zastanówmy się: co się dzieje kiedy ów przycisk zostaje uruchomiony?

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:

void returnFile( String filename, OutputStream out)

throws FileNotFoundException, IOException {

FileInputStream fis = null;

try {

fis = new FileInputStream(filename);

byte[] buf = new byte[4 * 1024]; // bufor 4-kilowy

int bytesRead;

while ((bytesRead = fis.read(buf)) != -1) {

out.write(buf, 0, bytesRead);

}

}

finally {

if (fis != null) fis.close();

}

}

Dodanie bloku finally nie zmienia faktu, że metoda ta propaguje wszystkie wyjątki do programu żądającego, jednak gwarantuje to, że przed propagacją, metoda otrzyma szansę zamknięcia otwartego FileInputStream.

Aplet przesyłający dane znakowe, wykorzystujący w tym celu PrintWriter, nie otrzymuje wyją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:

out.println("<H2>Here's the solution for your differential equation:</H2>");

if (out. checkError ()) return;

preliminaryCalculation() ;

out.print(" "); // treść wypełniająca, dodatkowe światło na stronie

//jest ignorowane w HTML-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.

Sześć sposobów wyciągania korzyści z apletów

Od czasu pierwszej edycji niniejszej książki aplety stały się, de facto, standardem dla Javowskiego konstruowania oprogramowania WWW po stronie serwera. „Ewolucyjna walka” rozgrywana pomiędzy apletami wykonywanymi na serwerach, zwykłymi apletami strony serwera oraz innymi, podłączalnymi, opartymi na Javie strukturami, ostatecznie się zakończyła, a aplety są jej zwycięstwami, obsługiwane dzisiaj przez każdy serwer WWW oraz każdy serwer aplikacji. Nowa dziedzina intensywnych innowacji usytuowana jest już poza apletami, na prezentacjach (obrazowaniach) oraz na poziomach osnów, w obszarze na którym osoby fizyczne oraz przedsiębiorstwa szukają sposobu zbudowania, na bazie apletów, tworzenia efektownych stron WWW.

Rozwiązania takie są bardzo potrzebne ponieważ proste podejście do generowania treści znaczników, 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, widokprezentacji, 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.

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.

Element Construction Set

Zestaw Element Construction Set (ECS), będący częścią „ApacheJakartaProject,” jest zestawem klas modelowanym po htmlKona, produkcie „WebLogic” (obecnie „BEA Systems”). ECS ma wiele ograniczeń, jednak rozwiązuje pewną grupę problemów, ponadto zapoznanie się z podejściem ECS jest dobrym punktem wyjściowym dla omawiania bardziej elastycznego XMLC (porównaj rozdział 17 „XMLC” oraz stronę http://jkarta.apache.org/ecs)

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.

* 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.

2 Część I Podstawy obsługi systemu WhizBang (Nagłówek strony)

2 C:\0-praca\Java Servlet - programowanie. Wyd. 2\r05-04.doc



Wyszukiwarka

Podobne podstrony:
r12-05, Programowanie, ! Java, Java Server Programming
r20-05, Programowanie, ! Java, Java Server Programming
O Autorach-05, Programowanie, ! Java, Java Server Programming
r07-05, Programowanie, ! Java, Java Server Programming
r03-05, Programowanie, ! Java, Java Server Programming
rE-05, Programowanie, ! Java, Java Server Programming
r19-05, Programowanie, ! Java, Java Server Programming
r17-05, Programowanie, ! Java, Java Server Programming
r11-05, Programowanie, ! Java, Java Server Programming
rD-05, Programowanie, ! Java, Java Server Programming
rF-05, Programowanie, ! Java, Java Server Programming
r04-05, Programowanie, ! Java, Java Server Programming
r13-05, Programowanie, ! Java, Java Server Programming
r01-05, Programowanie, ! Java, Java Server Programming
r10-05, Programowanie, ! Java, Java Server Programming
r02-05, Programowanie, ! Java, Java Server Programming
rB-05, Programowanie, ! Java, Java Server Programming
r12-05, Programowanie, ! Java, Java Server Programming
05 Programowanie

więcej podobnych podstron