r06 05 (13)


W tym rozdziale:

Rozdział 6.

Przesyłanie treści multimedialnej

Do tej pory wszystkie aplety, które pisaliśmy odsyłały standardową stronę HTML. Sieć WWW nie składa się jednak tylko z HTML-u, dlatego właśnie w niniejszym rozdziale zajmiemy się bardziej ciekawymi aspektami odsyłania apletowego. Rozpoczniemy od omówienia protokołu WAP oraz języka znaczników WML używanego w telefonii komórkowej oraz urządzeniach przenośnych i poznamy jak aplety mogą wykorzystywać tą technologię. Następnie pokażemy jak tworzyć dynamicznie, obrazy z apletów aby wygenerować wykresy i manipulować zdjęciami. Pod koniec rozdziału dowiemy się kiedy i jak przesyła się skompresowane odpowiedzi, ponadto zbadamy użycie odpowiedzi wieloczęściowych w celu wdrażania serwera cyklicznego.

WAP i WML

Protokół Aplikacji Bezprzewodowej (WAP) jest de facto standarem dla dostarczania łączności internetowej do telefonów komórkowych, pejdżerów oraz do Osobistych Asystentów Cyfrowych (PDA) w sieciach komunikacji bezprzewodowej na całym świecie. Protokół ten został stworzony przez Ericssona, Nokię, Motorolę oraz Phone.com (wcześniej „Unwired Planet”), które to firmy rozsądnie wyraziły chęć stworzenia protokołu standardowego, a nie własnych, konkurujących między sobą protokołów. Owe cztery firmy stworzyły razem forum WAP (http://www.wapforum.org), demokratycznie zarządzaną organizację, która dzisiaj liczy już 400 członków.

Protokół WAP składa się z szeregu specyfikacji, opisujących projektowanie aplikacji przeznaczonych do uruchamiania w sieciach komunikacji bezprzewodowej. Protokół WAP obejmuje zarówno poziom aplikacji (język znaczników WML oraz język skryptów WMLScript, znane pod wspólną nazwą Środowisko Aplikacji WWW lub WAE) jak i stosowanej sieci transportu poziomów (protokołów: WDP, WTLS, WTP oraz WSP). Kaskada WAP jest odpowiednikiem kaskady protokołu WWW, tak jak to zostało zaprezentowane na rysunku 6.1.

0x01 graphic

Rysunek 6.1.

Kaskady: WWW i WAP

Podstawowa różnica pomiędzy WAP-em a WWW polega na tym, że WAP jest zoptymalizowany do nisko-przepustowej komunikacji bezprzewodowej. Dla przykładu WAP używa formatu binarnego dla struktur odpowiedzi i zlecenia, zamiast formatu tekstowego przez WWW.

Brama WAP odgrywa rolę pośrednika pomiędzy siecią WAP a siecią WWW. Zadaniem bramy jest konwertowanie zleceń WAP na zlecenia WWW oraz odpowiadające im odpowiedzi WWW na odpowiedzi WAP. O Bramie WAP można powiedzieć, iż jest ona „konwertorem kaskady protokołu”* Bramy WAP udostępniają urządzeniom WAP dostęp do standardowych serwerów WWW. Zostało to zaprezentowane na rysunku 6.2.

0x01 graphic

Rysunek 6.2.

Rola Bramy WAP

Prostą sprawą jest konstruktor bezprzewodowy — możemy zignorować protokół warstwy transportowej. Brama WAP „dba” o to, aby zlecenie urządzenia wyglądało jak zlecenie HTTP. Warstwą na której musimy się skoncentrować jest warstwa aplikacji, w której zamiast generowania HTML-u, generujemy WML.

WML

Urządzenia przenośne są znacznie bardziej ograniczone niż komputery osobiste. Mają one zwykle wolne procesory, małe pojemności pamięci, małe wyświetlacze oraz niezwykle ograniczoną przepustowość. Z powodu podobnych ograniczeń urządzenia WAP nie współdziałają ze zwykłymi treściami HTML i treściami obrazkowymi. Zamiast tego używają one języka znaczników telefonii komórkowej (WML) — dla treści tekstowej, języka WMLScript dla wykonywania skryptów oraz Bezprzewodowej Mapy Bitowej (Wireless Bitmap) WBMP — monochromatycznego formatu obrazu dla grafiki.

WML jest aplikacją XML, podobną do HTML lecz z mniejszą ilością znaczników. Można tutaj zastosować porównanie do talii kart, gdzie każda karta odpowiada ekranowi strony, a każda strona zbiorowi kart, które mogą zostać natychmiast przesłane. Przesyłanie zawartości talii za jednym razem, zamiast jednej karty, minimalizuje opóźnienie (okres potrzebny do obejrzenia kolejnej karty) podczas „łączenia się” z treścią.

Przykład 6.2 ukazuje statyczny dokument WML, który spełnia rolę „minibarmana”. Wyświetla listę drinków do wyboru, a następnie składniki wybranego drinka.* Zwróćmy uwagę, iż jest to dokument XML z dobrze znanym DTD.

Przykład 6.1.

„Minibarman WML”, drinks.xml

<?xml versions"1.0"?>

<!DOCTYPE wml PUBLIC

"-//WAPFORUM//DTD WML 1.1//EN"

"http: / /www. wapforum.org/DTD/wml_l.1. xml" >

<wml>

<card id="Barman" title="Wybierz Drinka">

<p>

Select a Drink:

<anchor>

Kamikaze <go href="#Kamikaze" />

</anchor><br/>

<anchor>

Margarita <go href="#Margarita" />

</anchor><br/>

<anchor>

Boilermaker <go href="#Boilermaker" />

</anchor><br/>

</P>

</card>

<card id="Kamikadze" title="Kamikadze">

<p>

To make a Kamikaze:<br/>

1 part Vodka<br/>

1 part Triple Sec<br/>

1 part Lime Juice<br/>

</P>

</card>

<card id="Margarita" title="Margarita">

<p>

To make a Margarita:<br/>

1 1/2 oz Tequila<br/>

1/2 oz Triple Sec<br/>

1 oz Lime Juice<br/>

Salt<br/>

</p>

</card>

<card id="Boilermaker" title="Boilermaker">

<p>

To make a Boilermaker :<br/>

2 oz Whiskey<br/>

10 oz Beer<br/>

</p>

</card>

</wml>

Dokument zawiera cztery karty. Pierwsza karta, wyświetlana domyślnie, ukazuje krótką listę drinków. Każda nazwa drinka to hiperłącze do następnej karty dokumentu, z nazwą karty podaną przy użyciu składni #Cardname. Każda późniejsza karta wyświetla składniki dla drinka. Cały dokument może być przesłany do urządzenia podczas jednej operacji, mimo iż w danym momencie będą dostępne tylko jego części.

Symulatory urządzeń WAP

Aby umożliwić wywoływanie aplikacji WWW, bez odwoływania się do fal powietrznych, różne firmy tworzą symulatory telefoniczne obsługujące WAP, oprogramowanie do uruchamiania na komputerach osobistych, które działają jak telefon (i nawet tak wyglądają). Na rysunku 6.3 zaprezentowano jak wygląda dokument na symulatorze Phone.com UP.Simulator (dostępnym na http://phone.com). Inne symulatory oraz zestawy projektowania WAP można znaleźć na następujących stronach: http://www.nokia.com, http://www.ericsson.com i, http:/www.motorola.com oraz na wielu innych. Niektóre standardowe przeglądarki WWW, włącznie z przeglądarką „Opera” również obsługują WML.

0x01 graphic

Rycina 6.3. „Drinkowe telefonowanie”

Podawanie treści WAP

Aby treść WAP została podana poprawnie, serwer musi przesłać plik WML z jawnym typem treści text/vnd.wap.wml. Niektóre serwery ustalają ten typ treści automatycznie na pliki .wml. Inne muszą być informowane o nowym typie. Serwer musi być powiadomiony o typie treści odnoszącym się do WAP, z kolejnymi elementami <mime-mapping> w deskryptorze wdrożenia web.xml serwera. Bit informacji web.xml z przykładu 6.2 dodaje właściwy typ WML-u, WMLScript oraz pliki bezprzewodowej Mapy Bitowej.

Przykład 6.2.

Dodawanie typów nośników WAP do web.xml

<!- ... ->

<mime-mapping>

<extension>

wml

</extension>

<mime-type>

text /vnd. wap. wml

</mime-type>

</mime-mapping>

<mime-mapping>

<extension>

wmls

</extension>

<mime-type>

text /vnd. wap. wmlscript

</mime-type>

</mime-mapping>

<mime-mapping>

<extension>

wbmp

</extension>

<mime-type>

image /vnd. wap. wbmp

</mime-type>

< /mime-mapping>

<!-- ... -->

Dodatkowo niektórzy programiści aplikacji WWW uważają, że dobrze jest dodać index.wml do domyślnej listy plików wyszukiwania w katalogu, znanej jako welcome file list. Blok deskryptora wdrożenia w przykładzie 6.3 ustawia welcome file list do index.html, index.htm oraz do index.wml, w takiej właśnie kolejności. Taka kolej rzeczy daje znać serwerowi o zleceniu na http://localhost/wap/index.wml jeżeli index.html oraz index.htm nie istnieją.

Przykład 6.3.

Dodawanie plików WAP Welcome Files do web.xml

<!-- ... -->

<welcome-file-list>

<welcome-file>

index.html

</welcome-file>

<welcome-file>

index.htm

</welcome-file>

<welcome-file>

index.wml

</welcome-file>

</welcome-file-list>

<!-- ... -->

Dynamiczna treść WAP

Z technicznego punktu widzenia nie ma prawie żadnej różnicy pomiędzy tym jak aplet podaje dynamiczną treść WAP a sposobem w jaki podawana jest dynamiczna treść WWW. Aby podać treść WAP aplet musi po prostu zmienić typ treści odpowiedzi na text.vnd.wap.wml a treść odpowiedz z HTML-u na WML. Dla apletu, zlecenie jest zwykłym HTTP; zewnętrzna Brama WAP obsługuje WML oraz konwersję kaskady protokołu WWW. Podczas gdy różnica techniczna jest niewielka, przypadków w których lepiej jest posłużyć się apletem dla dynamicznego tworzenia treści może być niewiele.

W sieci WWW, nie ma praktycznie żadnych sankcji za kontaktowanie się z serwerem tylko po to aby wykonać mało znaczące zadania czy uaktualnienia strony. Przy zastosowaniu WAP-u, sankcje te mogą zostać ustanowione. W wyniku tego proste zadania takie jak: nawigacja kartowa i kontrola poprawności danych formularzowych, które w sieci WWW mogłyby angażować aplet, są najlepiej przeprowadzane w WAP przy użyciu metafory kartowej WML oraz zdolności wykonywania skryptów po stronie klienta WMLScript. Dla przykładu poprzedni aplet „barman” używa zestawu kart w celu podawania drinków bez kontaktowania się z serwerem.

Aplety nie pozostają oczywiście bez użyteczne. Aplet (lub inny skrypt serwera) jest potrzebny do generowania talii kart zawierających informacje dynamiczne pobrane z bazy danych. Aplety są również potrzebne do obsługi zleceń na dane zbyt duże aby zmieścić się na zestawie kart. Urządzenia są często ograniczone do przechowania tylko 1400 bitów skompilowanych danych strony.

Na przykładach 6.4 i 6.5 zaprezentowano, formularz WML oraz aplet generujący WML, które razem dostarczają aplikację wyszukania kodu kierunkowego. Klient może wprowadzić do formularz telefoniczny kod kierunkowy, przedłożyć go do apletu oraz dowiedzieć się który stan lub region odpowiada temu kodowi. Osoby posiadające telefony obsługujące WAP mogą wykorzystać tą aplikację do fizycznej lokalizacji jakiegokolwiek numeru identyfikacyjnego rozmówcy.

Przykład 6.4.

Wykorzystanie WML-u w celu uzyskania o kodu kierunkowego

<?xml version="1.0"?>

<!DOCTYPE wml PUBLIC

"-//WAPFORUM//DTD WML 1.1//EN"

"http: //www. wapforum. org/DTD/wml_l.1. xml">

<wml>

<card id="AreaCode" title="Wprowadź Kod Kierunkowy">

<do type="akceptuj" label="Wprowadź">

<go href="aplet /AreaCode?code=$(code)"/>

</do>

<p>

Enter an Area Code: <input type="tekst" nazwa="kod"/>

</p>

</card>

</wml>

Ten dokument WML przechowuje prosty formularz z obszarem wejściowym tekstu. Niezależnie od tego jaki kod kierunkowy zostanie wprowadzony, zostaje przesłany do apletu AreaCode jako parametr code. Aby utworzyć ręcznie ciąg zapytań używamy zmiennej zastąpienia $(code).

Przykład 6.5.

Wykorzystywanie WAP do zlokalizowania kodu kierunkowego

import java.io.*;

import java.util.*;

import javax.servlet.*;

import javax.servlet.http.*;

public class AreaCode extends HttpServlet {

Properties lookup = new Properties() ;

public void init() {

// Przenieś poniższe dane nieprzetworzone do a Listy właściwości

// szybkiego odszukania

for (int i = 0; i < data.length; i++) {

Object[] record = data[i];

String state = (String) record[0] ;

int[] codes = (int[]) record[1] ;

for (int j = 0; j < codes.length; j++) {

lookup.put(String.valueOf(codes[j]),state);

}

}

}

public void doGet(HttpServletRequest req, HttpServletResponse res)

throws ServletException, IOException {

res.setContentType("tekst/vnd.wap.wml") ;

PrintWriter out = res.getWriter();

String msg = null;

String code = req.getParameter("kod") ;

String region = null;

if (code != null) {

region = lookup.getProperty(code) ;

}

out.println("<?xml version=\"1.0\"?>") ;

out.println ("<!DOCTYPE wml PUBLIC " +

"\"-//WAPFORUM//DTD WML 1.1//EN\" " +

" \" http: / /www.wapforum. org/DTD/wml_l.1.xml \" >") ;

out.println("<wml>") ;

out.println("<card id=\"Code\" title=\"Code\">") ;

out.println(" <p>") ;

out.println (" Numer kierunkowy '" + code + '"<br/>");

if (region != null) {

out.println(" jest " + region + ",<br/>");

}

else {

out.println(" jest nie ważny.<br/>");

}

out.println(" </p>") ;

out.println("</card>") ;

out.println("</wml>") ;

}

// Nieprzetworzony numer kierunkowy dla każdego regionu

private Object[][] data = new Object[][] {

{ "Toll Free", new int[] { 800, 855, 866, 877, 888 } },

{ "Alabama", new int[] { 205, 256, 334 } },

{ "Alaska", new int[] { 907 } },

{ "Alberta", new int[] { 403, 780 }},

{ "Arizona", new int[] {480, 520, 602, 623 } },

{ "Arkansas", new int[] { 501, 870 } },

{ "British Columbia", new int[] { 250, 604 } },

{ "California", new int[] { 209, 213, 310, 323, 369, 408, 415, 424,

510,530, 559, 562, 619, 626, 627, 650, 661, 707, 714,

760,805,818,831,858, 909, 916, 925, 949 } },

{ "Colorado", new int[] { 303, 719, 720, 970 }},

{ "Connecticut", new int[] { 203, 475, 860, 959 }},

{ "Deleware", new int[] { 302 } },

{ "District of Columbia", new int[] {202 } },

{ "Florida", new int[] { 305, 321, 352, 407, 561, 727, 786, 813,

850, 863,904, 941, 954 } },

{ "Georgia", new int[] { 229, 404, 478, 678, 706, 770, 912 }},

{ "Hawaii", new int[] { 808 } },

{ "Idaho", new int[] { 208 } },

{ "Illinois", new int[] { 217, 224, 309, 312, 618, 630, 708,

773,815,847}},

{ "Indiana", new int[] { 219, 317, 765, 812 }},

{ "Iowa", new int[] { 319, 515, 712 } },

{ "Kansas", new int[] { 316, 785, 913 } },

{ "Kentucky", new int[] { 270, 502, 606, 859 } },

{ "Louisiana", new int[] { 225, 318, 337, 504 } },

{ "Maine", new int[] { 207 } },

{ "Manitoba", new int[] { 204 } },

{ "Maryland", new int[] { 240, 301, 410, 443 } },

{ "Massachusetts", new int[] { 413, 508, 617, 781, 978 } },

{ "Michigan", new int[] { 231, 248, 313, 517, 586, 616, 734,

810,906,} },

{ "Minnesota", newint[] { 218, 320, 507, 612, 651, 763, 952 } },

{ "Mississippi", new int[] { 228, 601, 662 } },

{ "Missouri", new int[] { 314, 417, 573, 636, 660, 816 } },

{ "Montana", new int[] { 406 } },

{ "Nebraska", new int[] { 308, 402 } },

{ "Nevada", new int[] { 702, 775 }},

{ "New Brunswick", new int[] { 506 } },

{"New Hanpshire", new int[] { 603 } },

{"New Jersey", new int[] { 201, 609, 732, 856, 908, 973 } }

{"New Mexico", new int[] { 505 } },

{"New York", new int[] { 212, 315, 347, 516, 518, 607, 631,

646,716,718, 845, 914, 917 }},

{"Newfoundland", new int[] { 709 } },

{"North Carolina", new int[] { 252, 336, 704, 828, 910, 919,918 }},

{"North Dakota", new int[] { 701 } },

{"Northwest Territories", new int[] { 867 } },

{"Nova Scotia", new int[] { 902 } },

{"Ohio",new int[] { 216, 234, 330, 419, 440, 513, 614, 740,937 } },

{"Oklahoma", new int[] { 405, 580, 918 ) },

{"Ontario", new int[] { 416, 519, 613, 647, 705, 807, 905 } !,

{"Oregon", new int[] { 503, 541, 971 }},

{"Pennsylvania", newint[] { 215, 267, 412, 484, 570, 610,717,

724,814,878,902 } }

{"Puerto Rico", new int[] { 787 } },

{"Quebec", new int[] { 418, 450, 514, 819 } },

{"Rhode Island", new int[] { 401 } },

{"Saskatchewan", new int[] { 306 } },

{"South Carolina", new int[] { 803, 843, 864 } },

{"South Dakota", new int[] { 605 } },

{"Tennessee", newint[] { 423, 615, 865, 901, 931 } },

{"Texas", new int[] { 210, 214, 254, 281, 361, 409, 469,

512,682,713 806, 817, 830, 832, 903, 915, 940, 956, 972 } },

{"US Virgin Islands", new int[] { 340 } },

{ "Utah", new int[] { 435,.801 } },

{"Vermont", new int[] { 802 } },

{"Virginia", new int[] { 540, 571, 703, 757, 804 } },

{"Washington", new int[] ( 206, 253, 360, 425, 509, 564 }},

{"West Virginia", new int[] ( 304 } },

{"Wyoming", new int[] ( 307 } },

{"Yukon Territory", new int[] { 867 } ),

};

}

Aplet ten otrzymuje zlecenie WAP jako zwykłe zlecenie HTTP GET. W swojej metodzie doGet() aplet ustawia typ treści na text/vnd.wap.wml, pobiera parametr code, lokalizuje odpowiadający kod poprzez tabelę powiązaną, i wreszcie generuje dokument WML zawierający region. Informacja numeru kierunkowego pochodzi z ręcznie wprowadzanej tablicy, która jest konwertowana na tabelę Properties po inicjalizacji apletu. „Zrzut” ekranu wydruku wyjściowego, pokazano na rysunku 6.4.

0x01 graphic

Rysunek 6.4.

Wykorzystanie WAP-u do lokalizacji rozmówców

Poznajmy WAP

Jeżeli chcielibyśmy nauczyć się czegoś więcej na temat tego jak się tworzy strony obsługujące urządzenia WAP, sięgnijmy po książkę Martina Frosta pt. „Learning WML & WMLScript”. Pomocne mogą również być dla nas strony źródłowe takie jak: http://www.wap-resources.net, http://AnywhereYouGo.com oraz http://www.wirelessdevnet.com. Istniej również administrowana przez „Sun” lista adresowa archiwizująca na http://archives.java.sun.com/archives/web-wireless.html.

Obrazki

Ludzie to istoty postrzegające świat głównie poprzez obraz — chcą oglądać, nie tylko czytać informacje. W konsekwencji jest prawie niemożliwym znalezienie strony WWW, nie wykorzystującej w jakiś sposób obrazków, niestety najczęściej są to obrazki wykonane nieprofesjonalnie. Podsumowując zacytujmy wyświechtany banał „Obraz jest wart tysiące słów”.

Szczęśliwie aplet może względnie łatwo przesłać obrazek jako odpowiedź. Właściwie już poznaliśmy aplet, który to robi: aplet ViewResource, opisany w rozdziale 4 „Odczytywanie informacji”. Jak na pewno pamiętamy aplet ten może odesłać wszystkie pliki pod dokumentem katalogu macierzystego serwera. Jeżeli zdarzy się tak, że plik będzie plikiem obrazu, aplet wykrywa ten fakt za pomocą metody getMimeType() a następnie ustawia swoją treść odpowiedzi za pomocą metody setContentType(), jeszcze przed przesłaniem nieprzetworzonych bitów do klienta.

Wymaganiem potrzebnym do zastosowania powyższej techniki potrzeba, aby żądane przez nas pliki były już zapisane na dysku, co nie zawsze jest naszym udziałem. Często aplet musi wygenerować lub przesunąć plik przed przesłaniem go klientowi. Rozpatrzmy przykład strony, która zawiera obrazek zegara analogowego, wyświetlającego aktualny czas. Oczywiście można by zapisać 720 obrazków (60 minut pomnożone przez 12 godzin) na dysku, a następnie wykorzystać aplet do wysłania jednego — właściwego. Lecz z pewnością nie powinniśmy tego robić. Zamiast tego powinniśmy napisać aplet, który będzie generował dynamicznie obrazek cyferblatu oraz wskazówek zegara lub wariantowo — aplet, który ładuje obrazek cyferblatu dodając tylko wskazówki. Będąc oszczędnymi programistami mamy również do dyspozycji buforowanie obrazka przez aplet (przez około minutę) ażeby zapisać cykle serwera.

Można sobie wyobrazić wiele powodów, dla których moglibyśmy chcieć, aby aplet odesłał obrazek. Dzięki generowaniu obrazków, aplet może wyświetlić rzeczy takie jak: stan magazynu z dokładnością do minuty, aktualny rezultat dla meczu bejzbolowego (opatrzonego w ikonki reprezentujące biegaczy na bazach) czy graficzną reprezentację stanu butelek Coca-Coli w automacie na bilon. Poprzez manipulowanie wcześniej-istniejącymi obrazkami aplet może dokonać nawet więcej. Może zmieniać ich podstawowe parametry takie jak: kolor, rozmiar lub wygląd; może również kombinować kilka obrazków w jeden.

Generowanie obrazków

Załóżmy, że mamy nieprzetworzone dane pikselowe, które chcemy komuś przesłać. Ktoś zapyta: ”W jaki sposób można tego dokonać?”. Już odpowiadamy. Załóżmy więc znowu, iż jest to 24-bitowy obrazek prawdziwych kolorów (3 bity na jeden piksel), oraz że ma on wymiary 100x100 pikseli. Moglibyśmy zrobić rzecz, która nasuwa się od razu — przesłać jeden piksel na jeden raz, w strumieniu 30.000 bitów. Jednak czy to wystarczy? Skąd odbiorca będzie wiedział co z nimi zrobić? Odpowiedź brzmi: nie będzie wiedział. Naszym obowiązkiem jest również poinformowanie, o tym, że przesyłamy wartości pikselowe prawdziwych kolorów, o tym że zaczynamy od lewego, górnego rogu, o tym że będziemy przesyłali wiersz po wierszu oraz o tym, iż każdy wiersz ma długość 100 pikseli. A co, jeżeli zdecydujemy się na przesłanie mniejszej liczby bitów stosując kompresję? W takim przypadku musimy poinformować jaki rodzaj kompresji stosujemy, aby odbiorca mógł zdekompresować obrazek. I nagle sprawa staje się bardziej skomplikowana.

Na szczęście jest to problem, który został rozwiązany i to rozwiązany na wiele różnych sposobów. Każdy format obrazu (GIF, JPEG, PNG, TIFF, itd.) odpowiada jednemu rozwiązaniu. Każdy format obrazu określa jeden standardowy sposób kodowania obrazków, tak że może on później zostać zdekodowany dla oglądania lub manipulowania. Każda z technik kodowania ma swoje zalety i ograniczenia. Dla przykładu kompresja używana dla kodowania GIF lepiej obsługuje obrazki generowane komputerowo, jednak forma GIF ograniczony jest do 256 kolorów. Kompresja używana dla kodowania JPEG, sprawdza się znowuż lepiej przy obrazkach foto-realistycznych, które zawierają miliony kolorów, jednak to lepsze działanie „okupione” jest zastosowaniem kompresji „stratnej”, która może rozmyć szczegóły zdjęcia. Kodowanie PIN (wymawiane jako „ping”) jest relatywnie nowym kodowaniem, którego celem jest zastąpienie GIF, jako że jest ono mniejsze, obsługuje miliony kolorów, wykorzystuje „bezstratną” kompresją, ponadto ma kanał alfa dla efektów przezroczystości — jest również wolna od problemów związanych z patentami, które były zmorą GIF.*

Zrozumienia na czym polega kodowanie obrazków, pomoże nam zrozumieć sposób w jaki aplety obsługują obrazki. Aplety takie jak ViewResource są w stanie odesłać wcześniej istniejące obrazki poprzez przesłanie swojej zakodowanej reprezentacji — niezmienionej do klienta, przeglądarka dekoduje wtedy obrazek do oglądania. Jednak aplet, który generuje lub modyfikuje obrazek musi skonstruować przedstawienie wewnętrzne obrazka, rozmieścić go, a następnie, przed przesłaniem go do klienta, zakodować go.

Obrazek „Hello World”

Przykład 6.6 ukazuje prosty przykład apletu, który generuje oraz odsyła obrazek GIF. Grafika ukazuje napis „Hello World!”, tak jak na rysunku 6.5.

Przykład 6.6.

Grafiki „Hello World”

import java.io.*;

import java.awt.* ;

import javax.servlet.* ;

import javax.servlet.http.* ;

import Acme.JPM.Encoders.GifEncoder;

public class HelloWorldGraphics extends HttpServlet {

public void doGet(HttpServletRequest req, HttpServletResponse res)

throws ServletException, IOException {

ServletOutputStream out = res.getOutputStream (); // binarny

wydruk wyjściowy!

Frame frame = null;

Graphics g = null;

try {

// Utwórz niepokazywaną ramkę

frame = new Frame () ;

frame. addNotify ();

// Pobierz obszar grafiki, przy użyciu ramki

Image image = frame.createlmage(400, 60);

g = image.getGraphics() ;

// Rysuj "Hello World!" do poza-ekranowego kontekstu grafiki

g. setFont (new Font (" Szeryfowy", Font. ITALIC ,48));

g.drawString("Hello World!", 10, 50);

// Zakoduj poza-ekranowy obrazek w GIF i prześlij go klientowi

res.setContentType("obrazek/gif");

GifEncoder encoder = new GifEncoder(image, out);

encoder.encode();

}

finally {

// Wyczyść zasoby

if (g != null) g. dispose();

if (frame i= null) frame.removeNotify() ;

}

}

}

Mimo iż aplet ten wykorzystuje zestaw java.awt, właściwie nigdy nie wyświetla okna na wyświetlaczu serwera. Nie wyświetla on również okna na wyświetlaczu klienta. Wykonuje on natomiast wszystkie operacje w poza-ekranowym kontekście grafiki, pozwalając przeglądarce wyświetlać obrazek. Strategia jest następująca: stworzenie poza-ekranowego obrazka, grafik do, umieszczenie jego grafik w kontekście, rysowanie do kontekstu grafiki, a następnie zakodowanie wynikłego obrazka dla transmisji do klienta.

0x01 graphic

Rysunek 6.5.

Grafiki „Hello World”

Otrzymywanie poza-ekranowego obrazka wiąże się z pokonaniem kilku trudności. W Javie, obrazek jest reprezentowany przez klasę java.awt.Image. Niestety w JDK 1.1, obiekt Image nie może zostać konkretyzowany bezpośrednio przez konstruktora. Musi on być uzyskany bezpośrednio przez fabryczną metodę taką jak np. createImage() Component lub metodę getImage() Toolkit. Jako że tworzymy nowy obrazek używamy do tego celu metody createImage(). Zwróćmy uwagę, iż zanim komponent butworzyć obrazek, jego własny peer musi już wtedy istnieć. W wyniku tego zanim utworzymy Image musimy utworzyć Frame, utworzyć peer ramki z wywołaniem do addNotify(), i wreszcie użyć ramki do stworzenia naszego Image.*

W JDK 1.2 cały proces został uproszczony, tak że Image może zostać utworzony bezpośrednio przez skonstruowanie nowego java.awt.image.BufferedImage. Nasze przykłady w tym rozdziale używają techniki frame.createImage() dla maksymalnej przenośności we wszystkich wersjach JDK.

Kiedy mamy już obrazek rysujemy na nim używając kontekstu grafiki, którą można pobrać przy użyciu wywołania do getGraphics(), metody Image. W tym przykładzie rysujemy tylko prosty strumień.

Po rysowaniu do kontekstu grafiki, wywołujemy setContentType() aby ustawić typ nośnika na image/gif ponieważ mamy zamiar użyć kodowania GIF. Dla przykładów w tym rozdziale użyliśmy kodera GIF napisanego przez Jefa Poskanzera. Jest to koder napisany solidnie i dostępny na stronie http://www.acme.com.

Zwróćmy uwagę, iż algorytm kompresji LZW użyty dla kodowania GIF jest chroniony patentami „Unisys'u” oraz IBM-a, które w zgodzie z fundacją wspierającą bezpłatne oprogramowanie, umożliwiają dostęp do bezpłatnego oprogramowania generującego format GIF. Koder Acme GIF wykorzystuje kompresję LZ, pozwoli to być może uniknązć patentu. Więcej informacji można znaleźć na stronach: http://www.fsf.org/philozofy/gif.html i http://www.burnallgifs.org. Oczywiście aplet może zakodować Image na każdy format obrazu. Dla treści WWW, JPEG i PNG są najbardziej odpowiednimi alternatywami dla GIF. Istnieje wiele dostępnych koderów JPEG i PNG. Dla użytkowników JDK wersji 1.2 i późniejszych, dostępny jest koder wbudowany w zestawie com.sun.image.codec.jpeg; kodery dla PNG i innych formatów dostępne są (dla JDK 1.2) w oficjalnym „Java Advanced Imagining API” na stronie http://java.sun.com/products/java-media/jai. Dla użytkowników, którzy potrzebują również obsługi JDK 1.1, istnieje program narzędziowy Java Image Management Interface (JIMI), wcześniej komercyjny produkt „Activated Intelligence”, obecnie „Sun library” dostępny bezpłatnie na stronie http://java.sun.com/products/jimi.

Aby zakodować obrazek tworzymy obiekt GifEncoder, przekazując mu obiekt Image i ServletOutputStream dla apletu. Kiedy wywołamy na obiekt GifEncoder metodę encoder(), obrazek zostanie zakodowany i przesłany do klienta.

Po przesłaniu obrazka, aplet wykonuje to, co wszystkie poprawnie zachowujące się aplety powinny zrobić: zwalnia swoje wszystkie zasoby graficzne. Będą one automatycznie odzyskane podczas odśmiecania, jednak zwolnienie ich pomaga systemom przy zasobach ograniczonych. Kod służący do zwolnienia zasobów, umieszczony został w bloku finally w celu zagwarantowania jego wywołania, nawet w przypadku gdy aplet zgłasza wyjątek.

Dynamicznie generowany schemat

Teraz przyjrzyjmy się apletowi, który generuje bardziej interesujący obrazek. Przykład tworzy wykres słupkowy, który porównuje jabłka do pomarańczy, w odniesieniu do ich rocznej konsumpcji.

Przykład 6.7.

Wykres porównujący jabłka do pomarańczy

import java.awt.*;

import java.io.*;

import javax.servlet.*;

import javax.servlet.http.*;

import Acme.JPM.Encoders.GifEncoder;

import javachart.chart.*; // z Techniki Wizualnej

public class SimpleChart extends HttpServlet (

static final int WIDTH = 450;

static final int HEIGHT = 320;

public void doGet(HttpServletRequest req, HttpServletResponse res)

throws ServletException ,IOException {

ServletOutputStrearo out = res.getOutputStream();

Frame frame = null;

Graphics g = null;

try {

// Utwórz prosty wykres

BarChart chart = new BarChart ("Jabłka i Pomarańcze");

// Nadaj mu tytuł

chart.getBackground().setTitleFont(new Font("Szeryfowy",

Font.PLAIN, 24));

chart.getBackground().setTitleString("Porównywanie Jabłek i

Pomarańczy");

// Pokaż miejsce i dostosuj jego objaśnienia

chart.setLegendVisible(true) ;

chart.getLegend().setL1X(0.4); // normalizowane z niższego lewego

chart.getLegend(),setLlY(0.75); // normalizowane z niższego lewego

chart.getLegend().setIconHeight(0.04);

chart.getLegend().setIconWidth(0.04);

chart.getLegend().setIconGap(0.02);

chart.getLegend().setVerticalLayout(false) ;

// Nadaj mu jego dane i oznaczenia

double[] appleData = {950, 1005, 1210, 1165, 1255};

chart.addDataSet("Jabłka", appleData) ;

double[] orangeData = {1435, 1650, 1555, 1440, 1595};

chart.addDataSet("Pomarańcze", orangeData) ;

String[] labels = {"1993", "1994", "1995", "1996", "1997"};

chart.getXAxis().addLabels(labels) ;

// Pokoloruj jabłka na czerwono, a pomarańcze na pomarańczowo

chart.getDatasets()[0].getGc().setFillColor(Color.red) ;

chart.getDatasets()[1],getGc().setFillColor(Color.orange);

// Nazwij osie

chart.getXAxis().setTitleString("Rok") ;

chart.getYAxis().setTitleString("Tony Skonsumowane") ;

// Nadaj odpowiedni rozmiar

chart.resize(WIDTH, HEIGHT);

// Utwórz niepokazywaną ramkę

frame = new Frame () ;

frame.addNotify() ;

// Pobierz obszar grafiki o odpowiednim rozmiarze, używając Ramki

Image image = frame.createlmage(WIDTH, HEIGHT);

g = image.getGraphics();

// Poproś wykres aby wrysował się w poza-ekranowy kontekst grafiki

chart.drawGraph(g) ;

// Zakoduj i odeślij to, co zostało namalowane

res. setContentType ("obraz/gif") ;

GifEncoder encoder = new GifEncoder(image, out);

encoder.encode() ;

}

finally (

// Wyczyść zasoby

if (g != null) g.dispose();

if (frame != null) frame.removeNotify();

}

}

}

Na rysunku 6.6 zostały zaprezentowane rezultaty. Właściwie nie ma potrzeby, aby wykres ten był generowany dynamicznie, mimo to pozwala osiągnąć wiele, bez zbędnego kodowania.

0x01 graphic

Rysunek 6.6.

Wykres porównujący jabłka do obrazków

Zasady są takie same: utworzyć poza-ekranowy obrazek oraz pobrać jego kontekst grafiki, rysować do tego kontekstu, a następnie zakodować rysunek do transmisji dla klienta. Różnica polega na tym, że aplet konstruuje obiekt BarChart do rysowania. W Javie dostępnych jest kilkanaście zestawów tworzących wykresy. Klasa BarChart, naszego przykładu pochodzi z zestawu „Visual Engineering's KavaChart” (wcześniej: JavaChart), który dostępny jest na stronie http://www.ve.com/kavachart. Jest to produkt komercyjny jednakże dla czytelników niniejszej książki został przyznany bezpłatny dostęp do „porcji” API zaprezentowanej w naszym przykładzie. Zestaw „KavaChart” zawiera również bezpłatny pakiet apletów generujących wykresy, ze strony http://www.ve.com/kavachart/servlets.html. Kolejnym dobry zestawem tworzącym wykresy jest zestaw „Sitraki”, dostępny na stronie http://www.sitraka.com (uprzednio KL Group).

Składanie obrazu

Jak dotąd rysowaliśmy nasze grafiki na puste obrazki. W tym rozdziale omówimy w jaki sposób przyjmować wcześniej istniejące obrazki i rysować na ich szczycie lub zespalać je w obrazki łączone. Przeanalizujemy również obsługę błędu w apletach odsyłających obrazki.

Rysowanie obrazków

Czasami aplet może czerpać korzyści z rysowania na górze istniejącego obrazka. Dobrym przykładem może tutaj być aplet -lokator składowy, który wie gdzie znajduje się każdy pracownik. Kiedy zapytamy gdzie znajduje się określony pracownik, wyświetli dużą, czerwoną kropkę nad jego biurem.

Jedną ze zwodniczo prostych technik dla rysowania wcześniej istniejących obrazków jest uzyskiwanie obrazków za pomocą Yoolkit.getDefaultToolkit().getImage(imagename), pobranie jego kontekstu grafiki przy pomocy wywołania do metody Image - getGraphics(), a następnie wykorzystanie odesłanego kontekstu grafiki do rysowania na górze obrazka. Niestety nie jest to takie proste jak się może wydawać. Powodem jest to, że nie można użyć metody getGraphics() jeżeli obrazek nie został utworzony przy pomocy metody Component createImage(). W przypadku pracy z narzędziami wzorowanymi na Windows AWT, konieczny jest zawsze własny peer, generujący w tle grafikę.

Istnieje jednak rzecz, którą można wykonać zamiast powyższego: pobranie wcześniej istniejącego obrazka poprzez metodę Toolkit.getDefaultToolkit().getImage(imagename), a następnie „powiedzenie” mu, aby narysował się na inny kontekst grafiki utworzony za pomocą metody createImage() Component, tak jak to zostało w poprzednich dwóch przykładach. Teraz możemy już wykorzystać omawiany kontekst grafiki do rysowania na górze oryginalnego rysunku.

Przykład 6.8 wyjaśnia na czym polega ta technika na konkretnym przykładzie. Prezentuje on aplet, który pisze „POUFNE” na każdym rysunku, który odsyła. Obrazek jest przekazywany do apletu jako informacja dodatkowej ścieżki; aplet załaduje obrazek z odpowiedniej lokalizacji pod serwerowym katalogiem źródłowym dokumentu, wykorzystując metodę getResource() w celu obsługi rozproszonego wywołania.

Przykład 6.8.

Rysowanie obrazka do oznaczenia go jako poufny

import java.awt.*;

import java.io.*;

import java.net.* ;

import javax.servlet.* ;

import javax.servlet.http.*;

import com.oreilly.servlet.ServletUtils;

import Acme.JPM.Encoders.GifEncoder;

public class Confidentializer extends HttpServlet {

Frame frame = null;

public void init() throws ServletException {

// Skonstruuj niepokazywaną ramkę wielokrotnego użytku

frame = new Frame();

frame.addNotify() ;

}

public void doGet (HttpServletRequest req, HttpServietResponse res)

throws ServletException, IOException {

ServletOutputStream out = res.getOutputStream();

Graphics g = null;

try {

// Pobierz lokalizację obrazka z informacji ścieżki

// Dla bezpieczeństwa użyj ServletUtils (Rozdział 4)

URL source =

ServletUtils.getResource(getServletContext(), req.getPathInfo()) ;

}

// Ładuj obrazek (z bajtów do obiektu obrazka)

MediaTracker mt = new MediaTracker(frame); // ramka działa jako

ImageObserver

Image image = Toolkit.getDefaultToolkit().getlmage(source);

mt.addlmage(image, 0) ;

try {

mt .waitForAll();

}

catch (InterruptedException e) {

res.sendError(res.SC_INTERNAL_SERVER_ERROR,

"Przerwanie podczas ładowania obrazka: " +

ServletUtils.getStackTraceAsString(e)) ;

return;

}

// Pobierz szerokość oraz długość

int w = image.getWidth(frame) ;

int h = image.getHeight(frame);

// Upewnij się, że odczytujemy poprawne dane obrazu

if (w <= 0 || h <= 0) {

res.sendError(res.SC_NOT_FOUND,

"Informacje dodatkowej ścieżki muszą wskazywać na poprawny

obrazek"),

return;

}

// Skonstruuj odpowiedni rozmiar poza-ekranowego kontekstu grafiki

Image offscreen = frame.createlmage (w, h);

g = offscreen.getGraphics();

// Rysuj obrazek do poza-ekranowego kontekstu grafiki

g.drawlmage( image, 0, 0, frame);

// Napisz POUFNE na jego górze

g.setFont(new Font("Monospaced", Font.BOLD | Fond.ITALIC, 30));

g.drawString("POUFNE", 10, 30);

// Zakoduj poza-ekranowe grafiki w GIF i prześlij je do klienta

res.setContentType("obraz/gif") ;

GifEncoder encoder = new GifEncoder(off screen, out);

encoder.encode()

}

finally {

// Wyczyść zasoby

if (g != null) g.dispose();

}

}

public void destroy() (

// Wyczyść zasoby

if (frame != null) frame.removeNotifyt() ;

}

}

Przykładowy wydruk wyjściowy można zobaczyć na rysunku 6.7.

0x01 graphic

Rysunek 6.7.

Rysowanie obrazka, w celu oznaczenia go jako poufny

Jak widać aplet ten przeprowadza każdy krok, dokładnie tak jak opisaliśmy to wcześniej, dodając trochę tematyki gospodarstwa domowego. Aplet tworzy swoją niepokazywaną ramkę w metodzie init().Tworzenie Frame, a następnie powtórne wykorzystywanie to optymalizacja, nie przeprowadzona uprzednio z powodów estetyczno-porządkowych. Dla każdego zlecenia aplet zaczyna od odczytania nazwy wcześniej istniejącego obrazka z informacji dodatkowej ścieżki, a następnie konwertuje ścieżkę do zasobu używając metody getResource(). Następnie pobiera odwołanie do obrazka za pomocą metody ToolkitgetImage() i fizycznie ładuje go do pamięci korzystając z pomocy MediaTracker.Zwykle ładowanie asynchroniczne nie jest problemem dla obrazka, ponieważ jego częściowe rezultaty malowane są wraz z postępem ładowania, jednak w tym przypadku obrazek jest malowany tylko raz i musimy mieć pewność, że zostanie on z góry załadowany w całości. W następnej kolejności aplet pobiera szerokość i długość ładowanego obrazka i tworzy poza-ekranowy obrazek do dopasowania. I wreszcie — decydująca chwila: ładowany obrazek jest rysowany na górze nowo-utworzonego, pustego. Potem wszystko dzieje się szybko. Aplet pisze swoje wielkie „POUFNE” i koduje obrazek do transmisji. Zauważmy w jaki sposób aplet radzi sobie z warunkami występowania błędu, wywołując metodę sendError(). Podczas odsyłania obrazków trudno o coś bardziej wyszukanego. Takie ujęcie sprawy pozwala apletowi na wykonanie tego, co uważa za słuszne.

Zestawianie obrazków

Aplet mają również możliwość zestawiania obrazków w jeden — obrazek kombinowany. Wykorzystując tą możliwość aplet-lokator składowy mógłby wyświetlić uśmiechnięta twarz pracownika — zamiast czerwonej kropki nad jego biurem. Technika używana przy zestawianiu obrazków, przypomina używaną do rysowania na górze obrazka: odpowiednie obrazki są ładowane, rysowane na utworzonym w odpowiedni sposób obiekcie Image, i w końcu obrazek ten jest kodowany do transmisji.

Przykład 6.9 pokazuje jak wykonać coś podobnego dla apletu, który wyświetla liczbę wywołań jako sekwencję osobnych liczbowych obrazków kombinowanych w jeden duży. Obrazki liczbowe, używane przez wspomniany aplet, dostępne są na http://www.geocities.com/SiliconValley/6742, razem z kilkoma innymi stylami.

Przykład 6.9.

Zestawianie obrazków do jednego — kombinowanego

import java.awt.*;

import java.io.*;

import java.net.*;

import javax.servlet.*;

import javax.servlet.http.* ;

import com.oreilly.servlet.ServletUtils;

import Acme.JPM.Encoders.GifEncoder;

public class GraphicalCounter extends HttpServlet {

public static final String DIR = "/images/odometer";

public static final String COUNT = "314159";

public void doGet(HttpServletRequest reg, HttpServletResponse res)

throws ServletException, IQException {

ServletOutputStream out = res.getOutputStream ();

Frame frame = null;

Graphics g = null;

try {

// Pobierz liczenia do wyśeietlenia, musi być wartość wyłączna w ciągu

// Lub użyj domyślnej

String count = (String)req.getQueryString();

if (count == null) count = COUNT;

int countlen = count.length() ;

Image images[] = new Image[countlen];

for (int i = 0; i < countlen; i++) {

URL imageSrc =

getServletContext().getResource(DIR + "/" + count. charAt(i) +

"GIF")

if (imageSrc == null) {

imageSrc = new URL (" plik:"); // znak-wypełniacz, błędy obsłuż póżniej

}

images[i] = Toolkit.getDefaultToolkit().getImage(imageSrc) ;

}

// Utwórz niepokazywaną ramkę

frame == new Frame () ;

frame.addNotify () ;

// Ładuj rysunki

MediaTracker mt = new MediaTracker(frame) ;

for (int i = 0; i < countlen; i++) {

mt.addImage(images[i], i) ;

}

try {

mt .waitForAll() ;

}

catch (InterruptedException e) {

res. sendError (res. SC_INTERNAL_SERVER_ERROR,

"Przerwanie podczas ładowania obrazka: " +

ServletUtils.getStackTraceAsString(e)) ;

return;

}

// Sprawdź czy nie pojawiają się problemy podczas ładowania obrazków

if (mt.isErrorAny()) {

//Mieliśmy problem ustalić który obrazek(i)

StringBuffer problemChars = new StringBuffer() ;

for (int i = 0; i < countlen; i++) {

if (mt.isErrorID(i)) {

problemChars.append(count.charAt(i)) ;

}

}

res.sendError(res.SC_INTERNAL_SERVER_ERROR,

"Nie można było załadować obrazka dla tych znaków: " +

probleinChars. toString ()) ;

return;

}

// Pobierz zbiorowy rozmiar obrazków

int width = 0;

int height = 0;

for (int i = 0; i < countlen; i++) {

width += images[i].getWidth(frame) ;

height = Math.max(height, images[i].getHeight(frame));

}

// Pobierz obszar grafiki do dopasowania, używając Ramki

Image image = frame.createImage(width, height);

g = image.getGraphics() ;

// Rysuj obrazki

int xindex = 0;

for (int i = 0; i < countlen; i++) {

g. drawlmage(images[i], xindex, 0, frame) ;

xindex += images [i].getWidth (frame);

}

// Zakoduj i odeślij kompozyt

res.setContentType("obrazek/gif") ;

GifEncoder encoder = new GifEncoder(image, out)

encoder.encode() ;

}

finally {

// Wyczyść zasoby

if (g != null) g.dispose();

if (frame != null) frame.removeNotify();

Strumień wyjściowy został pokazany na rysunku 6.8.

0x01 graphic

Rysunek 6.8.

Kombinowanie obrazków do graficznego licznika

Aplet ten pobiera liczbę do wyświetlenia poprzez odczytanie nieprzetworzonego ciągu zapytań. Dla każdej liczby w liczeniu, aplet wyszukuje i ładuje odpowiadający jej obrazek liczbowy z katalogu podanego przez DIR. (DIR znajduje się zawsze pod serwerowym katalogiem macierzystym dokumentu. Jest on podany jako ścieżka wirtualna i konwertowany na abstrakcyjną ścieżkę zasobu.) Następnie oblicza kombinowaną szerokość i maksymalną wysokość wszystkich tych obrazków i konstruuje poza-ekranowy rysunek do dopasowania. Aplet rysuje każdy obrazek liczbowy, na przemian, od lewa do prawa. W końcu koduje obrazek do transmisji.

Aby na coś się przydać aplet ten musi zostać wywołany przez inny aplet, który zna liczbę wywołań, która ma być wyświetlona i umieszcza tą liczbę w ciągu zapytań. Dla przykładu aplet ten może zostać wywołany przez stronę JSP lub inną, dynamicznie utworzoną stronę przy użyciu składni jak poniżej:

<IMG SRC="/aplet/Licznik Graficzny?121672">

Aplet obsługuje warunki powstania błędu w ten sam sposób, w jaki robi to aplet poprzedni, poprzez wywołanie metody sendError() i pozostawienie jej serwerowi aby zachowywał się w poprawny sposób.

Obrazki — efekty

Dowiedzieliśmy się już w jaki sposób aplety mogą tworzyć i kombinować obrazki. W tym podrozdziale dowiemy się jak apety mogą wykonywać specjalne efekty związane z obrazkami. Aplet może na przykład ograniczyć czas transmisji obrazka, poprzez zmianę jego rozmiaru przed transmisją. Może on również dodać specjalne cieniowanie do obrazka, aby przypominał on przycisk, który można nacisnąć. Dla przykładu przyjrzyjmy się w jaki sposób aplet może konwertować obrazek kolorowy na czarno biały.

Konwertowanie obrazka na „czerń i biel”

Na przykładzie 6.10 został ukazany aplet, który przed odesłaniem obrazka, konwertuje go na skalę czarno-białą. Aplet wykonuje ten efekt właściwie bez tworzenia poza-ekranowego kontekstu grafiki. Zamiast tego tworzy obrazek używając specjalnego ImageFilter (filtru obrazu). (Celowo nie zamieszczamy tutaj obrazków, ponieważ nie wyglądały by one najlepiej w czarno białej książce).

Przykład 6.10.

Efekt obrazkowy — konwertowanie obrazka na czarno białą skalę

import java.awt.* ;

import java.awt. image.* ;

import java.io.* ;

import java.net.*;

import javax.servlet.*;

import javax.servlet.http.*;

import com.oreilly.servlet.ServletUtils ;

import Acme.JPM.Encoders.* ;

public class Decolorize extends HttpServlet {

public void doGet(HttpServletRequest req, HttpServletResponse res)

throws ServletException, IOException {

res.setContentType("obraz/gif") ;

ServletOutputStream out = res.getOutputStream();

// Pobierz lokalizację obrazka z informacji ścieżki

URL source = ServletUtils.getResource(getServletContext(),

req.getPathInfo()) ;

if (source == null) {

res.sendError(res.SC_NOT_FOUND,

"Informacja dodatkowej ścieżki musi wskazywać na dodatkowy

obrazek");

return;

}

// Skonstruuj niepokazywaną ramkę

// Nie będzie żadnej addNotify()ponieważ jej peer nie jest

potrzebny

Frame frame = new Frame () ;

// Ładuj obrazek

Image image = Toolkit.getDefaultToolkit().getImage(source);

MediaTracker mt = new MediaTracker(frame);

mt. addImage (image, 0);

try {

mt .waitForAll<) ;

}

catch (InterruptedException e) {

res. sendError (res. SC_INTERNAL_SERVER_ERROR,

"Przerwanie podczas ładowania pliku: " +

ServletUtils.getStackTraceAsString(e)) ;

return;

}

// Pobierz rozmiar obrazka

int width = image. getWidth( frame) ;

int height = image.getHeight(frame);

// Upewnij się, że odczytujemy poprawne dane

if (width <= 0 || height <= 0) {

res.sendError(res.SC_NOT_FOUND,

"Informacje dodatkowej ścieżki muszą wskazywać na poprawny

obrazek") ;

return;

}

// Utwórz obrazek do dopasowania, zastosuj filtr

Image filtered = frame.createImage(

new FilteredImageSource(image.getSource(),

new GrayscaleImageFilter()));

//Zakoduj i odeślij przefiltrowany obrazek

GifEncoder encoder = new GifEncoder(filtered, out);

encoder.encode() ;

}

}

Kod dla tego apletu pokrywa się w większości z kodem przykładu Confidentialer. Główna różnica została zaprezentowana poniżej:

// Utwórz obrazek do dopasowania, Zastosuj filtr

Image filtered = frame.createImage(

new FilteredImageSource(image.getSource(),

new GrayscaleImageFilter())) ;

Aplet nie używa metody ComponentcreateImage(int, int), którą wykorzystywaliśmy dotychczas. Zamiast tego czerpie korzyści z innej metody ComponentcreateImage(ImageProducer). Aplet tworzy „image producer” za pomocą FilteredImageSource, które następnie przekazuje obrazek poprzez GrayscaleImageFilter. Filtr konwertuje każdy kolorowy piksel na jego czarno biały odpowiednik. Dzięki temu obrazek jest konwertowany na skalę czarno białą w czasie kiedy jest tworzony. Kod dla GrayscaleImageFilter został pokazany na przykładzie 6.11.

Przykład 6.11.

Klasa „GrayscaleImageFilter”

import java.awt.* ;

import java.awt.image.*;

public class GrayscaleImageFilter extends RGBImageFilter {

public GrayscaleImageFilter() {

canFilterIndexColorModel = true;

}

// Konwertuj kolorowe piksele na skalę czarno białą

// Algorytm jest zgodny ze specyfikacją NTSC

public int filterRGB(int x, int y, int pixel) {

// Pobierz średnie natężenie RGB

int red = (pixel & OxOOffOOOO) » 16;

int green = (pixel & OxOOOOffOO) » 8;

int blue = pixel & OxOOOOOOff;

int luma = (int) (0.299 * red + 0.587 * green + 0.114 * blue);

// Odeślij wartość luma dla każdego komponentu RGB

// Uwaga: (Przezroczystość) Alfa jest zawsze ustalona na wartość

maksymalną (nie przeżroczysta)

return (Oxff « 24) | (luma « 16) | (luma « 8) | luma;

}

}

Dla każdej wartości w mapie kolorów, filtr ten otrzymuje wartość pikselową i odsyła nową przefiltrowaną wartość pikselową. Dzięki ustawieniu zmiennej canFilterIndexColorModel na true, zaznaczamy, że filtr ten działa na mapie kolorów a nie na poszczególnych wartościach pikselowych. Wartość pikselowa podana jest jako 32-bitowe int, gdzie pierwszy bajt reprezentuje wartość (przezroczystości) alfa, drugi bajt natężenie czerwieni, trzeci oktet natężenie zieleni, a czwarty bajt natężenie koloru niebieskiego. Aby konwertować wartość pikselową na skalę czarno białą, natężenia czerwieni, zieleni i koloru niebieskiego muszą zostać ustalone na takie same wartości. Można również obliczyć wartość średnią dla wartości czerwieni, zieleni oraz koloru niebieskiego, a następnie zastosować średnią wartość natężenia każdego koloru. Spowodowałoby to konwersję obrazka na skalę czarno białą. Jeżeli jednak weźmiemy pod uwagę sposób, w jaki ludzie postrzegają kolory (i inne czynniki), okaże się jednak, że potrzebna jest średnia ważona. Ustawianie według priorytetów 0,299 0,587 0,114, które zostało tutaj zastosowane odpowiada temu, które jest stosowane przez Komitet Państwowego Systemu Telewizyjnego USA (National Television Systems Committee) dla telewizji czarno białej. Więcej na ten temat można znaleźć w książce Charlesa A. Poyntona pt. „A Technical Introduction to Digital Video” oraz na stronie http://www.color.org.

Buforowanie przekonwertowanego obrazka

Proces tworzenia i kodowani obrazka może być rzeczą kosztowną, biorąc pod uwagę zarówno czas jak i cykle CPU serwera. Buforowanie zakodowanych obrazków zawsze znacznie podnosi wydajność. Zamiast wykonywania wszystkich czynności dla każdego zlecenia, rezultaty mogą zostać zapisane i przesłane ponownie dla późniejszych zleceń. Idea tarczy zegara, o której wspomnieliśmy wcześniej może tutaj być idealnym przykładem. Obrazek zegara musi być tworzony co najwyżej raz na minutę. Wszystkim innym zleceniom złożonym w tej minucie może zostać przesłany ten sam obrazek. Wykres dla tabulacji głosowania stanowi następny przykład. Jest on tworzony raz, a następnie zmieniany wtedy kiedy przychodzą nowe głosy.

Nadklasa com.oreilly.servlet.CacheHttpServlet, z rozdziału 3 „Czas istnienia (cykl życia) apletu” dostarcza prosty mechanizm buforujący dla obrazków oraz dla tekstu. Aplet generujący obrazek zegara mógłby rozszerzyć CacheHttpServlet oraz wdrożyć metodę getLastModified(), odsyłającą czas, w którym został oddany ostatni głos.

Nasz przykład Decolorize mógłby rozszerzyć CacheHttpServlet za pomocą metody getLastModified(), która odsyła czas ostatniej zmiany zasobu obrazka. Niestety CacheHttpServlet buforuje tylko ostatnią odpowiedź, dając niewiele korzyści, w (prawdopodobnej) sytuacji, w której aplet DeColorize będzie wywołany na więcej niż jeden obrazek. DeColorize powinien prawdopodobnie używać bardziej wyszukanego algorytmu buforującego do obsługi obrazków wielokrotnych. Cykl życia apletu sprawia, że jest to całkiem proste. Nasz aplet DeColorize może zapisać każdy przekonwertowany obrazek tablicę bitów, przechowywaną w Hashtable nastrojonego przez nazwę obrazka. Najpierw aplet musi utworzyć zmienną kopii Hashtable. Musi ona zostać zdeklarowana poza metodą doGet():

Hashtable gifs = new Hashtable() ;

W celu wypełnienia tej tablicy-przewodnika musimy utrwalić zakodowane grafiki. Tak więc zamiast podawania GifEncoder ServletOutputStream, podajemy mu ByteArrayOutputStream. Następnie kodujemy obrazek za pomocą metody encode(), zakodowany obrazek jest przechowywany w ByteArrayOutputStream. Potem składujemy utrwalone bity w tablicy-przewodniku, by w końcu napisać je do ServletOutputStream i odesłać obrazek do klienta. Oto nowy kod do kodowania, składowania oraz odsyłania przefiltrowanego obrazka:

// zakoduj, składuj i odeślij przefiltrowany obrazek

ByteArrayOutputStream baos = new ByteArrayOutputStream(1024;; / 1K initial

GifEncoder encoder = new GifEncoder(filtered, baos);

encoder.encode () ;

gifs.put(source, baos) ;

baos.writeTo(out) ;

Taki kod wypełnia tablicę-przewodnika zakodowanymi obrazkami nastrojonymi przez nazwę obrazka. Teraz, tak jak wcześniej w aplecie, możemy przejść bezpośrednio do pamięci podręcznej, kiedy zostałaby poproszona o odesłanie wcześniej zakodowanego obrazka. Kod ten powinien znajdować się bezpośrednio po kodzie wykonanym jeżeli source==null (źródło =zero).

// Zwarcie jeżeli to zostało wykonane wcześniej

if (gifs.containsKey(source)) {

ByteArrayOutputStream baos = (ByteArrayOutputStream) gifs.get(source);

baos.writeTo(out);

return;

}

W trakcie podobnych modyfikacji każdy obrazek, który znajdzie się w pamięci podręcznej jest szybko odsyłany, bezpośrednio z pamięci.

Oczywiście buforowanie obrazków wielokrotnych pociąga za sobą zużywanie ogromnych ilości pamięci. Buforowanie pojedynczego obrazka nie jest problemem, jednakże aplet taki jak ten powinien użyć w tym celu jakiejś metody. Dla przykładu mógłby on buforować jedynie 100 ostatnich obrazków, na które składano zlecenia. Bardziej rozbudowana wersja tego apletu, mogłaby również sprawdzać znacznik czasu pliku, aby upewnić się, że oryginalny obrazek nie zmienił się od czasu buforowania czarno białej wersji.

Treść skompresowana

Zestaw java.util.zip został wprowadzony w JDK 1.1. Zestaw ten zawiera klasy, które obsługują odczytywanie i pisanie formatów kompresji ZIP i GZIP. Mimo iż zestaw ten został dodany w celu obsługi Plików Archiwalnych Javy (Java Archive Files) — JAR, zapewnia on również apletowi wygodny, standardowy sposób przesyłania treści skompresowanej.

Końcowy użytkownik nie widzi żadnej różnicy pomiędzy treścią skompresowaną, a treścią nie skompresowaną, ponieważ pierwsza jest dekompresowana przez przeglądarkę przed wyświetleniem. Jednak mimo, iż prezentuje się tak samo, może przysłużyć się użytkownikowi poprzez ograniczanie czasu ładowania treści z serwera. Dla bardzo „kompresowalnych” treści, takich jak HTML, kompresja może zredukować czas transmisji poprzez dyrektywę rozmiaru. Uświadomijmy sobie, że dynamiczne kompresowanie treści zmusza serwer do wykonywania dodatkowej pracy, tak więc ewentualne przyspieszenie transmisji musi wiązać z obniżeniem wydajności serwera.

Jak pamiętamy, aplet może przesyłać nagłówek Content-Type jako część swojej odpowiedzi, ażeby poinformować klienta o tym jaki typ ma przesyłana informacja. Aby przesłać treść skompresowaną, aplet musi również przesłać nagłówek Content-Encoding, aby poinformować klienta o schemacie, zgodnie z którym treść została zakodowana. Możliwe schematy kodowania specyfikacji HTTP 1.1 to: gzip (lub x-gzip), compress (lub x-compress) oraz deflate.

Nie wszyscy klienci rozumieją wszystkie kodowania. Aby poinformować aplet o tym, który schemat kodowania jest rozumiany przez klienta, klient może przesłać nagłówek Accept-Encoding, który określa akceptowane przez klienta schematy kodowania jako oddzielaną przecinkami listę. Nie wszystkie przeglądarki (wyłączając, kilka które obsługują kodowanie skompresowane) dostarczają ten nagłówek. Na ten moment aplet musi zdecydować, że bez tego nagłówka nie prześle treści skompresowanej lub że musi zbadać nagłówek User-Agent, aby stwierdzić czy przeglądarka obsługuje kompresję. Większość spośród obecnych, popularnych przeglądarek (jednak nie wszystkie) obsługuje kodowanie gzip, żadna nie obsługuje jednakże kodowania kompresji, a tylko MIcrosoft Internet Explorer 4 lub 5 (w Windowsie) obsługuje kodowanie typu deflate. W prostym określeniu zdolności przeglądarki, w tym tego czy obsługuje GZIP, można posłużyć się „BrowserHawk4J” — produktem „Cyscape”. Ten komercyjny produkt wykrywa obsługę GZIP, jak również wiele innych właściwości przeglądarki, takich jak: typ przeglądarki, wersję przeglądarki, system operacyjny klienta, zainstalowane moduły dodatkowe, szerokość i wysokość przeglądarki, szerokość i wysokość monitora klienta, wyłączenie cookies, zablokowany JavaScript, a nawet prędkość połączenia. Więcej na: http://www.cysape.com/products/bh4j.*

Mimo iż uzgadnianianie, który format kompresji ma zostać użyty może wiązać się ze sporą ilością logiki, właściwie rzecz biorąc trudno sobie wyobrazić łatwiejszy sposób przesyłania treści skompresowanej. Aplet po prostu zawija swój servletOutputStream przy pomocy GZIPOutputStream lub ZIPOutputStream. Pamiętajmy aby wywołać out.close() kiedy nasz aplet pisze wydruk wyjściowy, tak żeby właściwy dla formatu kompresji znak kończący został napisany.

Przykład 6.12 ukazuje aplet ViewResource z rozdziału 4, powtórnie napisany, tak żeby mógł przesyłać skompresowaną treść kiedy jest to możliwe. Nie zamieszczamy tym razem zrzutu ekranu, ponieważ nie ma na nim nic ciekawego do oglądania. Tak jak powiedzieliśmy wcześniej, użytkownik końcowy nie jest w stanie rozpoznać czy serwer przesłał treść skompresowaną — może z wyjątkiem ograniczonych czasów ładowania.

Przykład 6.12.

Przesyłanie treści skompresowanej

import java.io.*;

import java.net.*;

import java.util.*;

import java.util.zip.*;

import javax.servlet.*;

import javax.servlet.http.*;

import com.oreilly.servlet.ServletUtils ;

public class ViewResourceCompress extends HttpServlet {

public void doGet(HttpServletRequest req, HttpServletResponse res)

throws ServletException, IOException (

OutputStream out = null;

// Wybierz właściwe kodowanie treści oparte na

// nagłówku klienta Accept-Encoding. Wybierz GZIP jeżeli nagłówek

// zawiera "gzip". Wybierz ZIP jeżeli nagłówek zawiera "compress".

// W przeciwnym wypadku nie wybieraj żadnej kompresji. Upewnij się,

że // Content-Encoding wykorzystuje przedrostek "x-" wtedy i

tylko wtedy // jeżeli robi to Accept-Encoding.

String encodings = req.getHeader("Accept-Encoding");

if (encodings != null && encodings.indexOf("gzip") != -1) {

//Startuj z GZIP

if (encodings.index0f("x-compress") != -1) {

res.setHeader("Content-Encoding", "x-compress") ;

}

else {

res.setHeader("Content-Encoding ", "compress") ;

}

out = new ZipOutputStream(res.getOutputStream()) ;

}

((ZipOutputStream)out),putNextEntry(new ZipEntry("nazwa pusta"));

}

else {

// Nie kompresuj

out = res.getOutputStreamO ;

}

res.setHeader("Vary", "Accept-Encoding") ;

// Pobierz zasób do przeglądania

URL url = null;

try {

url=ServletUtils.getResource(getServletContext(), req.getPathInfo());

}

catch (IOException e) (

res.sendError(

res. SC_NOT_FOUMD,

"Informacje dodatkowej ścieżki muszą wskazywać na poprawny zasób do przeglądania: " +

e.getMessaget));

}

// Połącz z zasobem

URLConnection con = url.openConnection();

con.connect() ;

// Pobierz i ustal typ zasobu

String contentType = con.getContentType();

res-setContentTypetcontentType);

// Odeślij zasób

try {

ServletUtils.retumURL(url, out);

}

catch (IOException e)

res.sendError(res.Sc_INTERNAL_SERVER_ERROR,

"Problem przy przesyłaniu zasobu: " + e.getMessage());

}

// Pisz znak kończący i zamknij strumień wyjściowy

out.closet);

}

}

Aplet rozpoczyna od zdeklarowania null OutputStream, a następnie ustala to OutputStream na GZIPOutputStream lub ZipOutputStream lub na ServletOutputStream, w zależności od otrzymanego nagłówka Accept-Encoding.

Jako że aplet wybiera wydruk wyjściowy, który ma zostać przez niego użyty, ustala odpowiedni nagłówek Content-Encoding. Podczas przesyłania treści skompresowanej, nagłówek ten musi być ustawiony dla klienta w sposób umożliwiający działanie odpowiedniego algorytmu dekompresji. Aplet ustala również nagłówek Vary na wartość Accept-Encoding, aby „być grzecznym” i poinformować klienta, że aplet różnicuje swój wydruk wyjściowy w zależności od nagłówka Accept-Encoding. Większość klientów ignoruje ten nagłówek.

Po tej wczesnej logice, aplet może traktować wydruk wyjściowy jako kolejny OutputStream. Aplet mógłby zawinąć strumień za pomocą PrintStream lub PrintWriter, mógłby również Przekazać go do GifEncoder. Jednak bez względu na to co zrobi, musi wywołać metodę out.close(), kiedy już zakończy przesyłanie treści. Wywołanie to pisze właściwy znak kończący do skompresowanego strumienia.

Jest jednak pewien rodzaj treści, która nie powinna być kompresowana. Dla przykładu obrazki GIF i JPEG, są już kompresowane jako część ich kodowania, tak więc powtórne ich kompresowanie jest bezcelowe (a niektóre przeglądarki mają problemy przy wyświetlaniu obrazków przesłanych w skompresowanej formie). Ulepszona wersja apletu FileViewCompressed byłaby w stanie wykryć kiedy odsyła obrazek, i zaniechałaby dalszej kompresji

Serwer cykliczny

Do tej pory każda strona, odesłana przez aplet zawierała tylko jeden typ treści. Jednak tak wcale nie musi być. Dlaczego aplet miałby nie odsyłać kilku stron, każda zawierająca inny typ treści, wszystkie znajdujące się w jednej odpowiedzi? To może wydawać się nie do zrealizowania, jednak przy użyciu techniki zwanej serwerem cyklicznym (server push) jest to całkiem proste.

W przypadku stosowania tej techniki serwer umieszcza na stosie lub przesyła sekwencję stron odpowiedzi do klienta. Porównajmy tą technikę do techniki z poprzedniego rozdziału — ściągania klienckiego, w przypadku której to techniki klient musi otrzymać lub ściągnąć każdą stronę z serwera. Mimo iż rezultaty zastosowania obu technik są podobne dla użytkownika — pojawienie się sekwencji stron, szczegóły wdrażania oraz ich poprawne zastosowania są odmienne dla każdej z nich.

Przy stosowaniu serwera cyklicznego, połączenie gniazdowe pomiędzy serwerem a klientem pozostaje otwarte do czasu przesłania ostatniej ze stron. Takie rozwiązanie daje serwerowi możliwość szybkiego przesyłania uaktualnień strony oraz kontrolę nad tym, w jakim czasie wspomniane uaktualnienia będą przesyłane. Z takimi możliwościami technika „serwer cykliczny” nadaje się idealnie do stron, które wymagają częstych uaktualnień (takich jak animacje wykonywane na poziomie podstawowym) lub dla stron, które wymagają kontrolowanych przez serwer, jednak nie częstych uaktualnień (takich jak uaktualnienia aktywnego statusu). Zwróćmy jednak uwagę, iż metoda serwer cykliczny nie jest jeszcze obsługiwana przez Microsoft Internet Explorera i powinno się unikać jej szerokiego stosowania jako, iż okazało się to szkodliwe dla liczenia dostępnego portu serwera.

Przy stosowaniu klienckiego ściągania. połączenie gniazdowe jest przerywane po każdej stronie, tak więc odpowiedzialność za uaktualnianie stron spada na klienta. Klient wykorzystuje wartość nagłówkową Refresh, przesłaną przez serwer, aby określić kiedy przeprowadzić swoje uaktualnienie, tak więc technika klienckie ściąganie jest najlepszym rozwiązaniem dla stron, które nie wymagają częstych uaktualnień lub, które mają uaktualnienia w znanych przerwach.

Serwer cykliczny może również okazać się pomocny dla animacji o ograniczonej długości oraz dla uaktualnień statusu, wykonywanych w czasie rzeczywistym. Weźmy dla przykładu aplet, który jest w stanie przesłać do serwera cztery najświeższe pogodowe mapy satelitarne, tworząc animację poziomu podstawowego. Spróbujmy sobie przypomnieć aplet PrimeSearcher z rozdziału 3, zastanówmy się w jaki sposób moglibyśmy zastosować serwer cykliczny, aby zaznaczyć ograniczoną liczbę klientów, bezpośrednio po tym jak serwer znajdzie każdą nową liczbę pierwszą.

Na przykładzie 6.13 został zaprezentowany aplet, który wykorzystuje serwer cykliczny do wyświetlania odliczania czasu pozostałego do startu rakiety. Rozpoczyna się to wysłaniem serii stron odliczających od 10 do 1. Każda strona zastępuje swoją poprzedniczkę. Kiedy odliczanie dotrze do 0, aplet przesyła zdjęcie startu rakiety. Aplet używa klasy użyteczności com.oreilly.servlet.MultipartResponse (pokazanej na przykładzie 6.14), aby rozwiązać problemy związane ze szczegółami serwera cyklicznego.

Przykład 6.13.

Odliczanie do startu rakiety

import java.io.*;

import javax.servlet.*;

import javax.servlet.http.*;

import com.oreilly.servlet.MultipartResponse;

import com.oreilly.servlet.ServletUtils;

public class Countdown extends HttpServlet {

static final String LAUNCH = "/images/launch.gif";

public void doGet(HttpServletRequest req, HttpServletResponse res)

throws ServletException, IOException {

ServletOutputStream out = res. getOutputStream (); / / trochę

// binarnego wydruku wyjściowego

// Przygotuj odpowiedź wieloczęściową

MultipartResponse multi = new MultipartResponse(res);

// Najpierw prześlij odliczanie

for (int i = 10; i > 0; i--) {

multi.startResponse("tekst/zwykły") ;

out.println(i + "...");

multi.endResponse() ;

try ( Thread.sleep(lOOO); } catch (InterruptedException e) { }

}

// Następnie prześlij obrazek startu

multi.startResponse("obraz/gif") ;

try {

ServletUtils.returnFile(req.getRealPath (LAUNCH), out) ;

}

catch (FileNotFoundException e) {

throw new ServletException ("Nie można było odnaleźć pliku:

" + e.getMessage());

}

// Pamiętaj aby zakończyć odpowiedź wieloczęściową

multi.finish() ;

}

}

Klasa MultipartResponse ukrywa większość paskudnych, uciążliwych szczegółów związanych ze stosowaniem techniki serwer cykliczny. Możemy swobodnie stosować tą klasę w naszych własnych apletach. Jak widzieliśmy na poprzednim przykładzie jej stosowanie jest całkiem proste.

Najpierw tworzy się obiekt MultipartResponse, przekazując mu obiekt odpowiedzi apletu. Klasa MultipartResponse wykorzystuje obiekt odpowiedzi aby załadować wydruk wyjściowy apletu oraz aby ustawić typ treści odpowiedzi. Następnie, dla każdej strony treści, zaczynamy od wywołania metody startResponse() i przekazania jej typu treści dla tej strony. Treść dla tej strony przesyłamy, jak zwykle do strumienia wyjściowego. Wywołanie do endResponse() kończy stronę i wysyła treść z bufora, tak że klient może ją zobaczyć. Na tym etapie możemy dodać wywołanie do metody sleep(), lub jakiś inny rodzaj opóźnienia, stosowanego do czasu w którym następna strona będzie gotowa do wysłania. Wywołanie do metody endResponse() nie jest konieczne, ponieważ metoda startResponse() rozpoznaje czy poprzednia odpowiedź została zakończona i zakańcza ją w razie potrzeby. W przypadku gdy spodziewamy się opóźnienia pomiędzy czasem zakończenia jednej odpowiedzi a początkiem następnej, powinniśmy jednak wywołać metodę endResponse(). Pozwala to klientowi na wyświetlanie ostatniej odpowiedzi, podczas oczekiwania na następną. Ostatecznie, po przesłaniu wszystkich stron, wywołanie do metody finish() kończy wieloczęściową odpowiedź i przesyła kod informujący klienta, że nie żadne odpowiedzi nie będą już przesyłane.

Przykład 6.14 zawiera kod dla klasy MultipartResponse.

Przykład 6.14.

Klasa „MultipartResponse”

package com.oreilly.servlet;

import java.io.* ;

import javax.servlet.* ;

import jvax.servlet.http.* ;

public class MultipartResponse {

HttpServletResponse res ;

ServletOutputStream out;

boolean endedLastResponse = true;

public MultipartResponse(HttpServletResponse response) throws

IOException {

// Zapisz obiekt odpowiedzi i strumień wyjściowy

res = response;

out = res.getOutputStream() ;

// Ustaw wszystko

res. setContentType ("mulltipart/x-mixed-replace; boundary=End") ;

out.println() ;

out.println (" --End") ;

}

public void startResponse(String contentType) throws IOException {

// W razie konieczności zakończ ostatnią odpowiedź

if (!endedLastResponse) {

endResponse() ;

}

// Rozpocznij następną

out.println("Content-Type"+ contentType) ;

out.println() ;

endedLastResponse = false;

}

public void endResponse() throws IOException (

// Zakończ ostatnią odpowiedź i opróżnij bufor, tak żeby klient

mógł // zobaczyć treść

out.println() ;

out.println("--End") ;

out.flush() ;

endedLastResponse = true;

}

public void finish() throws IOException {

out.println("--End-") ;

out.flush();

}

}

* Bramy WAP są zwyczajowo dostarczane przez właściciela sieci bezprzewodowej. Czytelników zainteresowanych utworzeniem swojej własnej bramy lub tym jak działają bramy odsyłamy do Bramy WAP zwanej „Kannel” dostępnej na stronie http://www.wapgateway.org.

* Pełna implementacja tej idei dostępna jest na stronie http://www.wap2bar.com/index.wml

* Więcej informacji na temat PNG można znaleźć na stronie: http://graphicswiz.com/png.

* Dla serwerów WWW działających na systemach Unix, własny peer ramki musi zostać utworzony w serwerze X. Następnie, w celu optymalnej ustawienia wydajności, należy upewnić się, że zmienna środowiskowa DISPLAY (która określa, który serwer X będzie użyty) nie jest ustawiona lub ustawiona na lokalny serwer X, który może wymagać zastosowania komputera centralnego x lub uwierzytelniania x.

Serwery „Headless”, mogą bez działającego serwera X, mogą użyć Xvfb (wirtualnego bufora ramki X) obsłużyć prace pomocnicze grafiki; należy tylko pamiętać, żeby wskazać DISPLAY na serwerze Xvfb.

* Jason jest dumny z tego, iż był on jednym z czołowych projektantów zatrudnionych do stworzenia BrowserHawk4J

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

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



Wyszukiwarka

Podobne podstrony:
2 13 Polityka medialna 28 05 13
prostata(11)05[1].13.03, weterynaria, 4 rok, chirurgia koni
Badanie płytą 16 05 13 MC 20 ( rondo 1 w wa)
05 (13)
Zad 8 05 13
AaOPC 2011.05.13 wyklad, Administracja UKSW II st nst 2010-2012, II semestr
TPL WYK 13 05 13 Zadania
r06 05 (23)
2009 05 13 Rozp MON wojskowe dokumenty osobiste
2009 05 13 POZ 08
2 11 Nowe technologie i cyfryzacja 15 05 13
r06-05-spr, ## Documents ##, Debian GNU Linux
konspekt DT # 05 13?s

więcej podobnych podstron