R05-05.DOC

(332 KB) Pobierz
Szablon dla tlumaczy

 

W tym rozdziale:

·         Struktura odpowiedzi

·         Przesyłanie standardowej odpowiedzi

·         Stosowanie trwałych połączeń

·         Buforowanie odpowiedzi

·         Kody statusu

·         Nagłówki HTTP

·         Rozwiązywanie problemów

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

 

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ść. Apl...

Zgłoś jeśli naruszono regulamin