PA165/Cvičení webové aplikace 1 - Servlet API 3.0
Obsah
Prostředí
- NetBeans 7.2
- Oracle JDK 7
- Tomcat 7
- Maven 3
module add netbeans-7.2 jdk-1.7.0_03 maven-3.0.4
Vytvoření projektu
Vytvořte v NetBeans nový projekt pro webovou aplikaci typu Maven tj. File - New project - Categories:Maven - Project: Web Application, položku Package zvolte jako cz.muni.fi.pa165.web1.
Vytvořený projekt zkuste spustit (klávesa F6), měl by se otveřít prohlížeč a v něm stránka s nápisem Hello World!.
úvodní stránka
Zeditujte soubor index.jsp a vyměňte jeho obsah za následující HTML5:
<%@page contentType="text/html; charset=utf-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <title>Domov</title> </head> <body> <h1>Sláva, funguje to!</h1> <ul> <li><a href="${pageContext.request.contextPath}/text/primo?a=1&b=ccc">text přímo ze servletu</a></li> <li><a href="${pageContext.request.contextPath}/text/zjsp">text z JSP</a></li> <li><a href="${pageContext.request.contextPath}/soubor">soubor</a></li> <li><a href="${pageContext.request.contextPath}/presmerovani">přesměrování</a></li> <li><a href="${pageContext.request.contextPath}/chranene">chráněné heslem</a></li> <li><a href="${pageContext.request.contextPath}/text/ble">chyba</a></li> </ul> </body> </html>
Výraz ${pageContext.request.contextPath} vkládá před relativní odkazy prefix servletové aplikace.
Servlet
V package cz.muni.fi.pa165.web1 vytvořte novou třídu servletu kliknutím pravým tlačítkem na balík, vyberte New - Java class a pojmenujte ji MyDemoServlet.
Přepište zdrojový kód třídy na:
package cz.muni.fi.pa165.web1; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.annotation.WebServlet; import javax.servlet.http.*; import javax.xml.bind.DatatypeConverter; import java.io.IOException; import java.io.PrintWriter; import java.net.URLEncoder; import java.util.*; import java.util.regex.*; import java.util.zip.*; @WebServlet(urlPatterns = {"/text/*", "/soubor/*", "/chranene/*", "/presmerovani/*"}) public class MyDemoServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { if ("/text".equals(request.getServletPath()) && "/primo".equals(request.getPathInfo())) { response.setContentType("text/html;charset=utf-8"); PrintWriter out = response.getWriter(); out.println("<pre>text přímo ze servletu"); out.println("serverInfo=" + getServletContext().getServerInfo()); out.println("parametry:"); for (String p : Collections.list(request.getParameterNames())) { out.println(p + "=" + request.getParameter(p)); } return; } else if ("/text".equals(request.getServletPath()) && "/zjsp".equals(request.getPathInfo())) { request.setAttribute("seznam", Arrays.asList("mléko", "rohlíky", "salám")); request.getRequestDispatcher("/stranka2.jsp").forward(request, response); return; } else if ("/soubor".equals(request.getServletPath())) { response.setContentType("application/zip"); response.setHeader("Content-disposition", "attachment; filename=\"neco.zip\""); ServletOutputStream sos = response.getOutputStream(); ZipOutputStream zos = new ZipOutputStream(sos); zos.putNextEntry(new ZipEntry("neco.txt")); zos.write("nějaký text".getBytes()); zos.putNextEntry(new ZipEntry("necojineho.txt")); zos.write("jiný text".getBytes()); zos.close(); return; } else if ("/presmerovani".equals(request.getServletPath())) { String url = request.getContextPath() + "/text/primo?z=" + URLEncoder.encode("?/ =", "utf-8"); response.sendRedirect(response.encodeRedirectURL(url)); } else if ("/chranene".equals(request.getServletPath())) { String authorization = request.getHeader("Authorization"); if (authorization == null) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.setHeader("WWW-Authenticate", "Basic realm=\"zadej heslo\""); return; } Matcher matcher = Pattern.compile("Basic (.*)").matcher(authorization); if (!matcher.matches()) { response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Spatna hlavicka WWW-Authenticate"); return; } response.setContentType("text/html;charset=utf-8"); PrintWriter out = response.getWriter(); String[] creds = new String(DatatypeConverter.parseBase64Binary(matcher.group(1))).split(":", 2); out.println("<pre>zadáno jméno a heslo " + creds[0] + ":" + creds[1]); return; } else { response.sendError(HttpServletResponse.SC_NOT_FOUND, "Nerozpoznane URL"); return; } } }
Opět spusťte aplikaci a proklikejte si odkazy.
Komentáře
Povšimněte si ve výše uvedeném kódu servletu následujících věcí:
- třída servletu je mapována na URL pomocí anotace @WebServlet
- mapované URL se skládá ze tří částí - contextPath, servletPath a pathInfo
- metoda request.getParameterNames() vrací z historických důvodů java.util.Enumeration, na Iterable ji zkonvertujeme pomocí Collections.list()
- všechny parametry requestu jsou typu String
- při přeposlání (forward) na jiný servlet/JSP se data předávají pomocí metody setAttribute()
- při zasílání binárního obsahu můžeme pomocí HTTP hlavičky Content-disposition určit, zda se má přímo zobrazit (hodnota inline) nebo uložit (hodnota attachment)
- při přesměrování (redirect) je nutné před URL přidat contextPath, URL prohnat metodou encodeRedirectURL() a hodnoty případných parametrů musí být URL-encoded
- místo obsahu a přesměrování můžeme poslat prohlížeči chybové hlášení metodou sendError()
JSP - Java Server Page
V předchozí ukázce nefungoval odkaz /text/zjsp, protože zatím neexistovala JSP stránka, na kterou se předávalo řízení.
Nyní vytvořte v adresáři src/main/webapp soubor stranka.jsp a zeditujte mu obsah na následující:
<%@page import="java.util.List"%> <%@page contentType="text/html; charset=utf-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <title>JSP stránka</title> <style> #nadpis { color: red; } table.zakladni { border-collapse: collapse; margin: 10px; } table.zakladni th, table.zakladni td { border: solid 1px black; padding: 4px; } </style> <script> console.log("ahoj"); </script> </head> <body> <h1 id="nadpis">JSP stránka</h1> <p>seznam</p> <table class="zakladni"> <% List<String> seznam = (List<String>) pageContext.findAttribute("seznam"); for(int i=0,n=seznam.size();i<n;i++) { %> <tr> <td><%=i%></td> <td><%=seznam.get(i)%></td> </tr> <% } %> </table> </body> </html>
Spusťte znovu aplikaci z vyzkoušejte stránku. Ve Firefoxu po zmáčknutí Ctrl+Shift+K nebo v Chrome po zmáčknutí F12 můžete vidět JavaScriptovou konzolu z výpisem skriptu.
Komentáře
- na začátku JSP stránky je důležité definovat kódování výstupu parametrem charset, jinak se použije iso-8859-1
- pomocí CSS selektoru #nadpis nastavujeme vzhled právě jednoho prvku s atributem id="nadpis"
- pomocí CSS selektoru .zakladni nastavujeme vzhled všech prvků s atributem class="zakladni"
- v JavaScriptu můžeme psát trasovací výpisy pomocí console.log()
- příkazy v jazyce Java se vkládají mezi <% %>
- v JSP vždy existuje implicitní proměnná pageContext typu PageContext
- v JSP vždy existuje implicitní proměnná out typu JspWriter
- místo <%out.print(i);%> lze zkráceně psát <%=i%>
- přímé vypisování textů je nebezpečné, mohou obsahovat speciální znaky z HTML <>& ale není standardní třída pro HTML-encoding pomocí entit < > &
JSP stránka s JSTL
Místo kodu v Javě lze výhodně využít tzv. JSTL (JSP Standard Tag Library). Ta však není standardně součástí servletového containeru.
Aktivace JSTL
Do souboru pom.xml přidejte tuto závislost před tag </dependencies>:
<dependency> <groupId>org.glassfish.web</groupId> <artifactId>javax.servlet.jsp.jstl</artifactId> <version>1.2.2</version> </dependency>
Použití
V servletu změňte forward ze stranka.jsp na stranka2.jsp a vytvořte soubor src/main/webapp/stranka2.jsp s následujícím obsahem:
<%@page contentType="text/html; charset=utf-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="f" uri="http://java.sun.com/jsp/jstl/fmt" %> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <!DOCTYPE html> <html> <head> <title>JSP stránka</title> <style> #nadpis { color: green; } table.zakladni { border-collapse: collapse; margin: 10px; } table.zakladni th, table.zakladni td { border: solid 1px black; padding: 4px; } </style> <script> console.log("ahoj"); </script> </head> <body> <h1 id="nadpis">JSP stránka</h1> <p>seznam</p> <table class="zakladni"> <c:forEach items="${seznam}" var="s" varStatus="i"> <tr> <td>${i.count}</td> <td><c:out value="${s}"/></td> </tr> </c:forEach> </table> </body> </html>
Komentáře
- na začátku JSP je třeba knihovny značek navázat na nějaké prefixy pomocí direktiv <%@ taglib %>
- značka <c:forEach> iteruje svoje tělo přes položky zadané atributem items, v něm lze výhodně použít jazyk EL
- značka <c:forEach> ukládá aktuální položku do atributu pageContext určeného atributem značky var
- pomocí atributu značky varStatus lze získat objekt typu LoopTagStatus, pomocí které lze získat pozici v rámci iterace
- značka <c:out> zajistí HTML-encoding speciálních znaků