Java EE: Serwlety i filtry serwletów Do realizacji projektu potrzebne jest zintegrowane środowisko programistyczne NetBeans 6.9 Celem ćwiczenia jest przedstawienie podstawowej technologii platformy Java EE, czyli technologii serwletów. Tutorial ilustruje klasyczne zastosowanie serwletów, tj. dynamiczne generowanie stron HTML. We współczesnych aplikacjach Javy EE opartych o wzorzec projektowy MVC, serwety pełnią funkcję kontrolera, odpowiadając za wstępne przetworzenie żądania, wywołanie logiki biznesowej i wybór strony widoku. Same widoki implementowane są np. w technologii JSP lub Facelets. 1. Utworzenie i uruchomienie pierwszego serwletu a) Uruchom środowisko NetBeans. Z menu File wybierz opcję New Project Wybierz kategorię Java Web i typ projektu Web Application. Kliknij przycisk Next >
b) W drugim kroku kreatora jako nazwę projektu podaj Serwlety. W trzecim kroku kreatora jako serwer aplikacji wybierz najnowszą dostępną wersję serwera GlassFish, pozostaw zaproponowaną ścieżkę kontekstu i upewnij się, że jako wersja Javy EE wybrana jest Java EE 6 Web. W czwartym kroku nie wybieraj żadnych frameworków. Kliknij przycisk Finish. c) W oknie Projects kliknij prawym klawiszem myszki na ikonie projektu i z menu kontekstowego wybierz New Servlet
d) Jako nazwę serwletu podaj HelloWorldServlet. Jako nazwę pakietu wpisz ai.servlets. Kliknij przycisk Next > e) W ostatnim kroku kreatora pozostaw odznaczoną opcję dodania informacji o serwlecie do pliku deskryptora instalacji web.xml. Zwróć uwagę na odwzorowanie serwletu na adres URL. Pozostaw odwzorowanie zaproponowane przez kreator i kliknij przycisk Finish. Komentarz: Do wersji Java EE 5 konfiguracja serwletów (i całego modułu webowego aplikacji Java EE) realizowana była w oparciu o plik web.xml. Od wersji Java EE 6 plik ten jest opcjonalny, gdyż podstawowa konfiguracja może być wyspecyfikowana
poprzez adnotacje w kodzie Java. Plik web.xml jest używany do nadpisania ustawień zawartych w aplikacji po jej instalacji (bez konieczności rekompilacji aplikacji) oraz dla ustawień zaawansowanych, których nie można zrealizować poprzez adnotacje. f) Po zakończeniu działania kreatora Kod źródłowy serwletu zostanie otwarty w edytorze kodu. Odszukaj adnotację specyfikującą nazwę serwletu i jego odwzorowanie. Przeanalizuj w jaki sposób kreator zapewnił, że serwlet będzie odsługiwał żądania GET i POST, reagując na nie w ten sam sposób. g) Usuń z klasy serwletu metody doget, dopost i processrequest. Następnie dodaj poniższą implementację metody doget. protected void doget(httpservletrequest request, HttpServletResponse response) response.setcontenttype("text/html"); response.setcharacterencoding("windows-1250"); PrintWriter out = response.getwriter(); out.println("<html>"); out.println("<head><title>hello World Servlet</title></head>"); out.println("<body>"); out.println("<h1>hello World!</h1>"); out.println("</body>"); out.println("</html>"); out.close(); h) Uruchom serwlet komendą Run File.
i) W oknie umożliwiającym modyfikację adresu URL, który ma być otwarty w przeglądarce (np. w celu dodania parametrów wywołania) pozostaw zaproponowany adres i kliknij OK. j) Efekt działania serwletu powinien być widoczny w przeglądarce. Obejrzyj komunikaty w oknie Output środowiska NetBeans. Zwróć uwagę, że w związku z uruchomieniem serwletu NetBeans wykonał następującą sekwencję czynności: 1) Kompilacja projektu 2) Instalacja (deployment) skompilowanego modułu na serwerze 3) Otwarcie przeglądarki z adresem URL wywołującym serwlet k) Informacje na temat procesu instalacji (i ewentualnych błędów) można obejrzeć w logu serwera aplikacji. Odszukaj węzeł reprezentujący wykorzystywany serwer aplikacji GlassFish w oknie Services i wywołaj okno prezentujące zawartość jego dziennika.
l) Wróć do edycji kodu klasy serwletu. Poprzez modyfikację adnotacji zmień domyślne odwzorowanie na /hws. Ponownie uruchom serwlet korygując adres wywołania, tak aby obejrzeć efekt działania serwletu w przeglądarce m) Powtórz kroki z punku l) dla następujących dwóch odwzorowań: *.hws, /hws/* n) Przywróć pierwotne odwzorowanie serwletu. 2. Serwlet generujący zawartość inną niż HTML a) Serwlety mogą generować inną zawartość niż dokumenty HTML, nawet niekoniecznie tekstową. Co więcej, o ile w generowaniu zawartości tekstowej (HTML, XML) w praktyce dziś są zastępowane przez inne technologie platformy Java EE, serwlety pozostają niezastąpione gdy do przeglądarki trzeba przesłać zawartość binarną (generowaną programowo lub np. pobraną z bazy danych). W tym kroku ćwiczenia utworzymy serwlet generujący dynamicznie obraz JPEG. Kliknij prawym klawiszem myszy na ikonie projektu i wybierz New Servlet. Podaj nazwę serwletu ImageServlet i umieść go w pakiecie ai.servlets. Kliknij przycisk Next >. Zaakceptuj domyślne odwzorowanie serwletu na adres URL. Kliknij przycisk Finish. b) Zamień automatycznie wygenerowany szkielet serwletu na poniższy kod package ai.servlets; import java.io.*; import java.net.*; import javax.servlet.*; import javax.servlet.http.*; import java.awt.*; import java.awt.image.*; import com.sun.image.codec.jpeg.*; import javax.servlet.annotation.webservlet; @WebServlet(name="ImageServlet", urlpatterns={"/imageservlet") public class ImageServlet extends HttpServlet { protected void doget(httpservletrequest request, HttpServletResponse response) response.setcontenttype("image/jpeg"); int width = 100; int height = 30; BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g = (Graphics2D)image.getGraphics(); g.setcolor(color.orange); g.fillrect(0,0,width,height); g.setcolor(color.red); g.setfont(new Font("Dialog", Font.BOLD, 12)); g.drawstring("hello World!", 10, height/2 + 4); g.setcolor(color.black); g.drawrect(0,0,width-1,height-1); g.dispose(); ServletOutputStream sos = response.getoutputstream(); JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(sos); encoder.encode(image); c) Kliknij prawym klawiszem myszy w oknie edytora kodu źródłowego i z menu kontekstowego wybierz opcję Run File. Zaobserwuj działanie serwletu.
3. Przekazywanie parametrów z formularza HTML do serwletu d) Kliknij prawym klawiszem myszy na ikonie projektu i wybierz New HTML. Jako nazwę dokumentu podaj form (rozszerzenie *.html zostanie dodane automatycznie). Umieść w dokumencie poniższy kod. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>html form</title> <meta http-equiv="content-type" content="text/html; charset=windows-1250"> </head> <body> <form action="helloworldservlet" method="get"> <table> <tr><td>name:</td><td><input type="text" name="name"/></td></tr> <tr><td>age: </td><td><input type="text" name="age" size="2"/></td></tr> <tr><td colspan="2"><input type="submit" value="send"/></td></tr> </table> </form> </body> </html> a) W edytorze kodu otwórz zawartość pliku HelloWorldServlet.java. Zmodyfikuj kod metody doget() w poniższy sposób. Zwróć uwagę na konieczność jawnego rzutowania parametrów innych niż łańcuchy znaków. response.setcontenttype("text/html"); response.setcharacterencoding("windows-1250"); PrintWriter out = response.getwriter(); String name = request.getparameter("name"); int age = Integer.parseInt(request.getParameter("age")); out.println("<html>"); out.println("<head><title>hello World Servlet</title></head>"); out.println("<body>"); out.println("<h1>hello World!</h1>"); out.println("<p>witaj, " + name + ", masz " + age + " lat</p>"); out.println("</body>"); out.println("</html>"); out.close(); b) Kliknij prawym klawiszem myszy na pliku form.html (w oknie Projects, gałąź Web Pages), z menu kontekstowego wybierz pozycję Run File. Wypełnij pola formularza i prześlij formularz do przetworzenia w serwlecie. Sprawdź czy serwlet poprawnie wygeneruje powitanie dla imienia zawierającego polskie znaki. c) Odszukaj w kodzie serwletu wiersz ustawiający kodowanie dla generowanej odpowiedzi. Następnie dodaj instrukcję w analogiczny sposób podającą informację o kodowaniu dla żądania (obiekt request). Zwróć uwagę aby informacja ta była ustawiona przed odczytem wartości parametrów z żądania. Ponownie uruchom formularz i sprawdź działanie serwletu dla imienia z polskimi znakami. d) Zmień w formularzu HTML metodę wywoływania serwletu na POST. Zainstaluj aplikację na serwerze. Ponownie otwórz formularz w przeglądarce i spróbuj wysłać formularz do serwletu. Zinterpretuj otrzymany komunikat.
e) Do klasy HelloWorldServlet dodaj poniższą metodę i ponownie przetestuj aplikację. protected void dopost(httpservletrequest request, HttpServletResponse response) this.doget(request, response); 4. Obsługa sesji w serwlecie a) Utwórz nowy serwlet i nazwij go HttpSessionServlet. Umieść nowo utworzony serwlet w pakiecie ai.servlets. Serwlet będzie umożliwiał przechowywanie krótkich notatek pomiędzy kolejnymi wywołaniami dokumentu. b) Jako kod serwletu podaj poniższy kod. Zwróć szczególną uwagę na obsługę sesji, utworzenie nowego obiektu do przechowywania notatek w przypadku gdy obiekt sesyjny jest pusty oraz konieczność każdorazowego utrwalenia obiektu sesyjnego. Uruchom serwlet i przetestuj jego działanie. package ai.servlets; import java.io.*; import java.util.arraylist; import javax.servlet.annotation.webservlet; import javax.servlet.*; import javax.servlet.http.*; @WebServlet(name="HttpSessionServlet", urlpatterns={"/httpsessionservlet") public class HttpSessionServlet extends HttpServlet { protected void doget(httpservletrequest request, HttpServletResponse response) HttpSession session = request.getsession(); ArrayList notes = (ArrayList)session.getAttribute("notes"); if (notes == null) { notes = new ArrayList (); session.setattribute("notes",notes); String note = request.getparameter("note"); if (note!= null) notes.add(note); PrintWriter out = response.getwriter(); out.println("<html>"); out.println("<body>"); out.println("<h1>my notes</h1>"); out.println("<ul>"); for (int i = 0; i < notes.size(); i++) out.println("<li>" + notes.get(i)); out.println("</ul>"); out.println("<form action='httpsessionservlet'>"); out.println("<input type='text' name='note'/><br/>"); out.println("<input type='submit' value='add note'/>"); out.println("</form>"); out.println("</body>"); out.println("</html>");
c) Interfejs Servlet API umożliwia proste zapisywanie dziennika aplikacji, który jest dostępny poprzez kontekst serwletu. Dodaj do klasy HttpSessionServlet poniższą metodę. Uruchom serwlet i w oknie dziennika zaobserwuj wyświetlony komunikat public void init() throws ServletException { this.getservletcontext().log("servlet initialized at " + new java.util.date()); 5. Współbieżne uruchomienie serwletu a) Utwórz nowy serwlet i nazwij go ConcurrentServlet. Umieść nowo utworzony serwlet w pakiecie ai.servlets. Zaakceptuj wszystkie pozostałe domyślne ustawienia. Jako kod źródłowy umieść poniższy kod. Zwróć uwagę, że serwlet implementuje wzorzec POST REDIRECT GET package ai.servlets; import java.io.*; import javax.servlet.annotation.webservlet; import javax.servlet.*; import javax.servlet.http.*; @WebServlet(name="ConcurrentServlet", urlpatterns={"/concurrentservlet") public class ConcurrentServlet extends HttpServlet { public String lastname; public void init() { lastname = "NoName"; protected void doget(httpservletrequest request, HttpServletResponse response) response.setcontenttype("text/html"); PrintWriter out = response.getwriter(); out.println("<html>"); out.println("<body>"); out.println("name: <b>" + this.lastname + "</b>"); out.println("</body>"); out.println("</html>"); out.close(); protected void dopost(httpservletrequest request, HttpServletResponse response) String paramname = request.getparameter("last_name"); this.lastname = paramname; response.sendredirect("concurrentservlet");
b) Dodaj do projektu dokument HTML, nazwij go test.html i umieść w nim poniższy kod <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title></title> <meta http-equiv="content-type" content="text/html; charset=windows-1250"> </head> <body> <form action="concurrentservlet" method="post"> Podaj imię: <input type="text" name="last_name"/> <input type="submit" value="wyślij"/> </form> </body> </html> c) W pierwszym kroku uruchom sam serwlet bezpośrednio ze środowiska NetBeans. Co widzisz na ekranie? Następnie, uruchom plik test.html i za jego pomocą uruchom serwlet. Czy wynik jest taki, jak się spodziewała(e)ś? W kolejnym kroku, zachowując otwarte okno pierwszej przeglądarki, uruchom drugą przeglądarkę i otwórz ponownie plik test.html, podając inną wartość parametru do serwletu. Na koniec, powróć do okna pierwszej przeglądarki i, nie wykonując żadnych innych czynności, odśwież okno. Jak wytłumaczysz wynik, który obserwujesz? Jak naprawić aplikację? 6. Obsługa ciasteczek a) Utwórz nowy serwlet i nazwij go TossCoinServlet. Umieść nowo utworzony serwlet w pakiecie ai.servlets. Zaakceptuj wszystkie pozostałe domyślne ustawienia. Jako kod źródłowy umieść poniższy kod. Serwlet w każdym żądaniu rzuca monetą i wyświetla wynik rzutu. Dodatkowo, liczba uzyskanych orłów i reszek jest przechowywana pod postacią ciasteczka (Cookie). Przeanalizuj dokładnie poniższy kod, zwróć szczególną uwagę na sposób odczytywania danych z ciasteczka. Po uruchomieniu kodu odśwież dokument kilkukrotnie w celu zaobserwowania wyników działania. package ai.servlets; import java.io.*; import javax.servlet.annotation.webservlet; import javax.servlet.*; import javax.servlet.http.*; @WebServlet(name="TossCoinServlet", urlpatterns={"/tosscoinservlet") public class TossCoinServlet extends HttpServlet { protected void doget(httpservletrequest request, HttpServletResponse response) response.setcontenttype("text/html"); PrintWriter out = response.getwriter(); int numberofheads = 0; int numberoftails = 0; Cookie[] cookies = request.getcookies(); if (cookies!= null) for (int i = 0; i < cookies.length; i++) { if (cookies[i].getname().equals("heads"))
numberofheads = Integer.parseInt(cookies[i].getValue()); if (cookies[i].getname().equals("tails")) numberoftails = Integer.parseInt(cookies[i].getValue()); String toss = (Math.random() <= 0.5)? "head" : "tail"; if (toss.equals("head")) numberofheads++; else numberoftails++; Cookie heads = new Cookie("heads", String.valueOf(numberOfHeads)); heads.setmaxage(integer.max_value); heads.setcomment("to store the observed number of heads"); response.addcookie(heads); Cookie tails = new Cookie("tails", String.valueOf(numberOfTails)); tails.setmaxage(integer.max_value); tails.setcomment("to store the observed number of tails"); response.addcookie(tails); out.println("<html>"); out.println("<body>"); out.println("coin toss: " + toss + "<br/>"); out.println("number of heads: " + numberofheads + "<br/>"); out.println("number of tails: " + numberoftails + "<br/>"); out.println("<a href=\"tosscoinservlet\">toss again</a>"); out.println("</body>"); out.println("</html>"); out.close(); 7. Kontrola sterowania. Zasięg żądania widoczności zmiennych. a) Utwórz nowy serwlet i nazwij go ServletA. Umieść nowo utworzony serwlet w pakiecie ai.servlets. Zaakceptuj wszystkie pozostałe domyślne ustawienia. Jako kod źródłowy umieść poniższy kod. Serwlet losuje czterocyfrową liczbę i przekazuje ją poprzez zasięg żądania widoczności zmiennych. Zwróć uwagę, że przekazać można tylko obiekt, a zatem liczba całkowita musi być przekazana pod postacią klasy opakowującej Integer. package ai.servlets; import java.io.*; import javax.servlet.annotation.webservlet; import javax.servlet.*; import javax.servlet.http.*; @WebServlet(name="ServletA", urlpatterns={"/servleta") public class ServletA extends HttpServlet { protected void doget(httpservletrequest request, HttpServletResponse response) ServletContext ctx = this.getservletcontext(); RequestDispatcher dispatcher = ctx.getrequestdispatcher("/servletb"); java.util.random randomizer = new java.util.random(); Integer pin = new Integer(randomizer.nextInt(9999)); request.setattribute("pin",pin); PrintWriter out = response.getwriter();
out.println("<html>"); out.println("<body>"); out.println("<h1>tekst wygenerowany przez ServletA</h1>"); dispatcher.forward(request, response); b) Utwórz drugi serwlet o nazwie ServletB. Umieść nowo utworzony serwlet w pakiecie ai.servlets. Zaakceptuj wszystkie pozostałe domyślne ustawienia Umieść w serwlecie poniższy kod. Zainstaluj aplikację w serwerze aplikacji. package ai.servlets; import java.io.*; import javax.servlet.annotation.webservlet; import javax.servlet.*; import javax.servlet.http.*; @WebServlet(name="ServletB", urlpatterns={"/servletb") public class ServletB extends HttpServlet { protected void doget(httpservletrequest request, HttpServletResponse response) java.text.decimalformat fmt = new java.text.decimalformat("0000"); String pin = fmt.format(request.getattribute("pin")); PrintWriter out = response.getwriter(); out.println("<h1>tekst wygenerowany przez ServletB</h1>"); out.println("wygenerowany PIN: " + pin); out.close(); c) Uruchom serwlet ServletA. Czy widzisz kod wygenerowany przez ServletA? Zamień wywołanie metody dispatcher.forward(request, response) na wywołanie metody dispatcher.include(request, response). Ponownie zainstaluj aplikację w serwerze aplikacji i uruchom ponownie ServletA. Czy tym razem widzisz wynik działania serwletu ServletA?
8. Filtry serwletów a) Interfejs Servlet API umożliwia definiowanie filtrów, które można łączyć w łańcuch wywoływanych kolejno komponentów. Filtry mogą być aplikowane do obiektu żądania i do obiektu odpowiedzi. Ostatnie ćwiczenie polega na dodaniu prostego filtru, który będzie automatycznie zliczał liczbę odwołań do każdego serwletu wchodzącego w skład aplikacji. b) Kliknij prawym klawiszem myszy na projekcie i wybierz New Other Przejdź do kategorii Web i zaznacz Filter Kliknij przycisk Next >
b) Jako nazwę filtru podaj LinkTrackFilter, umieść filtr w pakiecie ai.servlets. Upewnij się, że oryginalne obiekty żądania i odpowiedzi nie zostaną opakowane. Kliknij przycisk Next >
c) W ostatnim oknie kreatora zdefiniuj odwzorowanie filtra na URL /* aby każde żądanie do dowolnego zasobu aplikacji było kierowane przez filtr. Nie zlecaj dodania informacji o filtrze do pliku konfiguracyjnego web.xml. Kliknij przycisk Finish. d) Jako kod źródłowy filtru umieść poniższy kod. package ai.servlets; import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.webfilter; @WebFilter(filterName="LinkTrackFilter", urlpatterns={"/*") public class LinkTrackFilter implements Filter { private FilterConfig filterconfig = null; private HashMap links = null; public void dofilter(servletrequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String link = ((HttpServletRequest)request).getRequestURL().toString(); int count = 0; if (links.containskey(link)) count = Integer.parseInt(links.get(link).toString()); links.put(link, Integer.valueOf(++count));
Iterator i = links.keyset().iterator(); while (i.hasnext()) { link = i.next().tostring(); count = Integer.parseInt(links.get(link).toString()); String str = link + " requested " + count + " times"; this.getfilterconfig().getservletcontext().log(str); chain.dofilter(request, response); public FilterConfig getfilterconfig() { return (this.filterconfig); public void setfilterconfig(filterconfig filterconfig) { this.filterconfig = filterconfig; public void destroy() { public void init(filterconfig filterconfig) { this.filterconfig = filterconfig; this.links = new HashMap(); c) Zainstaluj aplikację w serwerze aplikacji wybierając opcję Deploy z menu kontekstowego węzła projektu w oknie Projects. Otwórz w przeglądarce kolejno serwlety ServletA, HttpSessionServlet, ImageServlet, po otwarciu każdego serwletu odśwież kilkukrotnie zawartość przeglądarki. W oknie dziennika aplikacji zaobserwuj wynik działania filtru.